Navigation
Branching Submenu
Animated tree navigation with keyboard support
detent-branching-submenu
Port status
- Catalog status
- implemented
- Origin
- upstream
- Phase
- 3
- Wave
- phase-3-overlays
- Group
- Navigation
6.9 KB gzip for the class module · inlines the shared spring
Variants
Recorded states
focus-visiblerecorded state; no setterreduced motionrecorded state; no setter
Usage
Import
import '/components/branching-submenu/define.js';
// or register it yourself
import { DetentBranchingSubmenu } from '/components/branching-submenu/branching-submenu.js';
Attributes
label
Properties
items
Slots
No slots are recorded for this component.
Events
No events are recorded for this component.
Example
Runnable example: site/examples/branching-submenu.ts. The snippet below is generated from the catalogued attributes.
<detent-branching-submenu
label="Branching navigation"
></detent-branching-submenu>
Property-only values
const element = document.querySelector('detent-branching-submenu');
if (element !== null) {
// items: BranchingSubmenuItem[]
element.items = [];
}
Source
The pinned upstream source for this row:
src/components/ui/BranchingSubmenu.tsxsha256 95144d7b4d80…
Native files
The native module and its artifacts:
src/components/branching-submenu/branching-submenu.tssrc/components/branching-submenu/styles.csssrc/components/branching-submenu/define.tsdocs/components/branching-submenu.mdsite/examples/branching-submenu.tstests/browser/branching-submenu.spec.tsevidence/components/branching-submenu.md
API
Boolean attributes follow HTML presence semantics: present means true, absent means false, and disabled="false" still means disabled.
Attributes
| Attribute | Property | Type | Default | Notes |
|---|---|---|---|---|
label | label | string | 'Branching navigation' | Kebab-case attribute; camelCase property. |
Properties
| Property | Type | Default | Notes |
|---|---|---|---|
items | BranchingSubmenuItem[] | demo items | Property-only: assign from script; not accepted as an attribute. |
Events
No custom events are recorded for this component.
Slots
No slots are recorded for this component.
CSS customisation
- Framer Motion springs/AnimatePresence ported to CSS transitions, WAAPI or the shared rAF spring helper.
- `lucide-react` icon components replaced with local SVG assets (ISC notice retained).
- React state/hooks replaced by element-owned state with pre-upgrade property preservation.
- items is plain data, rendered as text. Upstream accepted a BranchingSubmenuItem[] too, but rendered through React; here labels and descriptions go through textContent, so a payload like <script>window.x = 1</script> stays literal text (R-1.1). Invalid entries are dropped with one warning each instead of throwing.
- Upstream motion is carried over: the selection pill glides between parents on springMorph (its layoutId); the branch content follows AnimatePresence mode="wait" on springGentle, the old branch springing out (opacity 0, y -6, scale 0.98, inert, focus moved to the active parent) before the new one springs in (y 6); and the children enter on upstream's tween (0.22 s, cubic-bezier(0.16, 1, 0.3, 1), index * 35 ms delay) through WAAPI, so the stagger no longer lingers as a transition-delay on hover. First paint does not animate (initial={false}). Not carried over: the containers' layout size animation, and the content exit when Escape closes the panel (it hides at once). Motion fidelity is a manual gate (PC-10.8).
- Reduced motion removes all movement. Upstream swapped the animated pill for a static one and kept an opacity cross-fade; here the pill, the content swap and the child entrance do not run under prefers-reduced-motion: reduce, so the final state is immediate and fully usable (PC-10.5).
- lucide-react's ChevronRight and Layers become one local, decorative inline chevron SVG (aria-hidden, PC-11.5). The Layers glyph next to the panel title is dropped: the panel is already labelled by panel-title.
- Palette. The upstream dark-only hex values are replaced by theme tokens so the component works in light and dark (PC-9.5).
- className is replaced by the documented parts and custom properties; no Tailwind and no React state/hooks are involved.
- Escape also returns focus. Upstream only set open = false; the native port moves focus back to the active parent so the keyboard path has a defined target (PC-12.4).
- No role="navigation" div. The region is a native <nav> element instead of a div[role="navigation"] (PC-12.1).
What it does
A labelled navigation region with a master list of parents and a branch panel that swaps its children when the active parent changes. Keyboard-explorable (Arrow keys, Enter, Escape), with a staggered child entrance and a responsive single-column stack.
Upstream behaviour
- Parent and child panels communicate hierarchy through position and stagger
- Arrow keys, Enter, and Escape support menu exploration
- Responsive layout stacks cleanly on narrow screens
Differences from upstream
- Framer Motion springs/AnimatePresence ported to CSS transitions, WAAPI or the shared rAF spring helper.
- `lucide-react` icon components replaced with local SVG assets (ISC notice retained).
- React state/hooks replaced by element-owned state with pre-upgrade property preservation.
- items is plain data, rendered as text. Upstream accepted a BranchingSubmenuItem[] too, but rendered through React; here labels and descriptions go through textContent, so a payload like <script>window.x = 1</script> stays literal text (R-1.1). Invalid entries are dropped with one warning each instead of throwing.
- Upstream motion is carried over: the selection pill glides between parents on springMorph (its layoutId); the branch content follows AnimatePresence mode="wait" on springGentle, the old branch springing out (opacity 0, y -6, scale 0.98, inert, focus moved to the active parent) before the new one springs in (y 6); and the children enter on upstream's tween (0.22 s, cubic-bezier(0.16, 1, 0.3, 1), index * 35 ms delay) through WAAPI, so the stagger no longer lingers as a transition-delay on hover. First paint does not animate (initial={false}). Not carried over: the containers' layout size animation, and the content exit when Escape closes the panel (it hides at once). Motion fidelity is a manual gate (PC-10.8).
- Reduced motion removes all movement. Upstream swapped the animated pill for a static one and kept an opacity cross-fade; here the pill, the content swap and the child entrance do not run under prefers-reduced-motion: reduce, so the final state is immediate and fully usable (PC-10.5).
- lucide-react's ChevronRight and Layers become one local, decorative inline chevron SVG (aria-hidden, PC-11.5). The Layers glyph next to the panel title is dropped: the panel is already labelled by panel-title.
- Palette. The upstream dark-only hex values are replaced by theme tokens so the component works in light and dark (PC-9.5).
- className is replaced by the documented parts and custom properties; no Tailwind and no React state/hooks are involved.
- Escape also returns focus. Upstream only set open = false; the native port moves focus back to the active parent so the keyboard path has a defined target (PC-12.4).
- No role="navigation" div. The region is a native <nav> element instead of a div[role="navigation"] (PC-12.1).