Navigation
Pill Navigation
Minimal shared-pill navigation continuity with fluid submenus
detent-pill-navigation
Port status
- Catalog status
- implemented
- Origin
- upstream
- Phase
- 2
- Wave
- phase-2-navigation
- Group
- Navigation
5.9 KB gzip for the class module · inlines the shared spring
Variants
Recorded states
expandedrecorded state; no setterselectedrecorded state; no setterreduced motionrecorded state; no setter
Usage
Import
import '/components/pill-navigation/define.js';
// or register it yourself
import { DetentPillNavigation } from '/components/pill-navigation/pill-navigation.js';
Attributes
default-sub-valuedefault-value
Properties
itemsonChange
Slots
No slots are recorded for this component.
Events
detent-change— { id: string; subId?: string }
Example
Runnable example: site/examples/pill-navigation.ts. The snippet below is generated from the catalogued attributes.
<detent-pill-navigation
default-sub-value
default-value
></detent-pill-navigation>
Property-only values
const element = document.querySelector('detent-pill-navigation');
if (element !== null) {
// items: PillNavigationItem[]
element.items = [];
// onChange: (id: string, subId?: string) => void
element.onChange = () => {};
}
Source
The pinned upstream source for this row:
src/components/ui/PillNavigation.tsxsha256 ee9aa1f8098f…
Native files
The native module and its artifacts:
src/components/pill-navigation/pill-navigation.tssrc/components/pill-navigation/styles.csssrc/components/pill-navigation/define.tsdocs/components/pill-navigation.mdsite/examples/pill-navigation.tstests/browser/pill-navigation.spec.tsevidence/components/pill-navigation.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 |
|---|---|---|---|---|
default-sub-value | defaultSubValue | string | undefined | Kebab-case attribute; camelCase property. |
default-value | defaultValue | string | first item id | Kebab-case attribute; camelCase property. |
Properties
| Property | Type | Default | Notes |
|---|---|---|---|
items | PillNavigationItem[] | demo items | Property-only: assign from script; not accepted as an attribute. |
onChange | (id: string, subId?: string) => void | undefined | Property-only: assign from script; not accepted as an attribute. |
Events
| Event | Upstream prop | Payload type | Notes |
|---|---|---|---|
detent-change | onChange | (id: string, subId?: string) => void | Composed, bubbles; fires only for a user action. |
Slots
No slots are recorded for this component.
CSS customisation
- React callback props become typed composed CustomEvents; programmatic state assignment emits none.
- 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.
- Controlled/uncontrolled React pair maps to initial-value attributes plus owned state and change events.
- `items` defaults to `[]`; upstream shipped a hard-coded demo list as its default, which is demo data, not API - the preview and example carry it.
- `default-value`/`default-sub-value` are initial-value attributes (they seed the first render, not re-applied), matching upstream's uncontrolled useState pair; there is no controlled value or programmatic selection setter.
- Both tiers use tablist/tab with a named submenu and roving tabindex: upstream's role=menu/menuitem, invalid aria-checked and redundant wrapper tabindex are removed (PC-12.6).
- ArrowDown/ArrowUp open a closed submenu instead of silently changing a hidden sub-selection.
- `PillSubItem.badge` is declared upstream but never rendered by the source, so it stays in the type and renders nothing.
- Both pills are single elements driven by the shared helper with the recorded springMorph (main) and springGentle (sub); AnimatePresence becomes a CSS entrance keyframe replayed when the active parent changes.
- Upstream's dark-only hex palette becomes --detent-color-*, and the submenu outline is raised to 3:1 because its raised surface matches the page in light mode.
What it does
A segmented pill bar with one shared indicator that preserves spatial continuity, plus a nested, fluid submenu with its own indicator and dual-tier keyboard navigation.
Upstream behaviour
- Single shared indicator preserves spatial continuity
- Interactive nested submenu with fluid spring entrance and layout morphing
- Dual-tier keyboard navigation for parent tabs and sub-items
Differences from upstream
- React callback props become typed composed CustomEvents; programmatic state assignment emits none.
- 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.
- Controlled/uncontrolled React pair maps to initial-value attributes plus owned state and change events.
- `items` defaults to `[]`; upstream shipped a hard-coded demo list as its default, which is demo data, not API - the preview and example carry it.
- `default-value`/`default-sub-value` are initial-value attributes (they seed the first render, not re-applied), matching upstream's uncontrolled useState pair; there is no controlled value or programmatic selection setter.
- Both tiers use tablist/tab with a named submenu and roving tabindex: upstream's role=menu/menuitem, invalid aria-checked and redundant wrapper tabindex are removed (PC-12.6).
- ArrowDown/ArrowUp open a closed submenu instead of silently changing a hidden sub-selection.
- `PillSubItem.badge` is declared upstream but never rendered by the source, so it stays in the type and renders nothing.
- Both pills are single elements driven by the shared helper with the recorded springMorph (main) and springGentle (sub); AnimatePresence becomes a CSS entrance keyframe replayed when the active parent changes.
- Upstream's dark-only hex palette becomes --detent-color-*, and the submenu outline is raised to 3:1 because its raised surface matches the page in light mode.