Pathfinderby Commenda
Components

Side nav

Application navigation with a compact rail, recursive groups, and native links.

Import SideNav from @commenda/pathfinder/side-nav. The application owns routes, active destination, permissions and persistence. No workspace providers, cookies or global shortcuts are required.

API reference

PropTypeBehavior
labelRequiredstringRequired navigation landmark name
sectionsRequiredreadonly SideNavSection[]IDs, optional label/collapsible, recursive items
activeIdRequiredstring|nullCurrent destination; receives aria-current=page
collapseConfigObjectView fields · 2 shapes{ value, onValueChange } or { defaultValue?, onValueChange? }; false initially
expansionConfigObjectView fields · 2 shapesSection and group IDs; collapsible sections start open
header| ReactNode | (( collapsed: boolean ) => ReactNode)Slots can adapt to the rail
footer| ReactNode | (( collapsed: boolean ) => ReactNode)Slots can adapt to the rail
modeConfigSegmentedControlPropsOptional view-mode switch; app supplies matching sections
renderLink( props: React.ComponentPropsWithRef<'a'> ) => React.ReactElementNext Link integration; item renderer takes precedence
classNamestringRoot nav classes; parent supplies available height
collapseConfigObject fields

Choose one of these 2 shapes. Do not combine fields excluded by the selected shape.

Option 1

FieldTypeRequirement
valuebooleanRequired
onValueChange(value: boolean) => voidRequired

Do not supply: defaultValue.

Option 2

FieldTypeRequirement
defaultValuebooleanOptional
onValueChange((value: boolean) => void)Optional

Do not supply: value.

expansionConfigObject fields

Choose one of these 2 shapes. Do not combine fields excluded by the selected shape.

Option 1

FieldTypeRequirement
valuereadonly string[]Required
onValueChange(value: readonly string[]) => voidRequired

Do not supply: defaultValue.

Option 2

FieldTypeRequirement
defaultValuereadonly string[]Optional
onValueChange((value: readonly string[]) => void)Optional

Do not supply: value.

An item has id, label, icon, optional count, unread, and disableConfig: { isDisabled, reason? }. Its kind distinguishes link with linkConfig, action with onAction, or group with recursive children and optional landing linkConfig. A landing link and its chevron are distinct targets. A group without a landing link expands on activation.

Keep section and item IDs stable and globally distinct. linkConfig supports href, target, rel, and a ref-forwarding render. Renderers must preserve supplied accessibility props, handlers and refs. Actions and links are mutually exclusive. Counts and unread state stay accessible in the rail.

Collapsed rail

Expanded width is 208px; the rail is 56px. Rows are 28px, icons 16px. In the rail, group activation expands navigation to reveal its children; groups with a destination preserve that link, and the Expand sidebar control reveals their descendants. Header/footer render functions prevent a long organization name from overflowing a rail.

Controlled state

Expanded · 2 expanded groups

Expansion and collapse are independent of the active route. The app may persist them per user; Pathfinder does not read browser storage. Overflow fades follow actual scroll position. Use an app-owned mobile navigation surface when the persistent sidebar does not fit; the component does not guess viewport breakpoints or mutate routes. Chat conversation history remains a separately composed pattern. Read the hierarchy rules.

Design rules

Figma guidance and reviewed implementation decisions for this component. Rule IDs remain stable for source comments and AI references.

Side navigation

  • NAV-001: Expanded 208px, rail 56px, pearl100 background, 12px inset, 24px section gaps. Header mark/wordmark and collapse control; optional Home/Chat SegmentedControl; footer account composition. Rows 28px, radius 6, horizontal inset 8, gap 12, icons 16.
  • NAV-002: Label/13 pearl900; icon pearl800. Hover pearl200, selected pearl300, selected label1000 and icon blue700. The landing link and expansion chevron share one continuous hover/selected row surface. Weight never changes. The docs prose mentions selected 200, but the reviewed component 714:15 uses 300; the implementation follows the component. Count Label/12, optional unread dot; rail uses tooltip and accessible metadata.
  • NAV-003: Stable globally distinct IDs, one active destination, real anchors, separate group chevrons. Routing, auth, persistence and fetching belong to the application. Collapse retains group/section state. Chat history is a separate composed pattern; do not embed an application’s routes or conversation API in SideNav.
  • NAV-004: Scroll fades match CommendaNavbar: 48px from the Soft Pearl 100 surface (#FCFBF8) to transparent, appearing only at overflowing edges. The parent supplies available height. No global keyboard shortcut, cookie or workspace provider is installed by the component.

On this page