Skip to content

Navigation List

Terminal-styled navigation sidebar list with selection, keyboard navigation, and composable item parts.

Preview

Installation

$pnpm exec dgadd add ui/navigation-list
[Installs to]src/components/ui/navigation-list[Item]ui/navigation-list

dgadd is not public on npm yet. Until the first release, pack @diffgazer/add from the repository and install that tarball into this app, which is what puts dgadd on pnpm exec.

UI components require Tailwind CSS v4. Local copy mode imports src/styles/styles.css; package mode uses @diffgazer/ui CSS once packages are available.

Usage

tsx
"use client";import { useState } from "react";import { NavigationList } from "@/components/ui/navigation-list";export default function NavigationListDefault() {  const [selected, setSelected] = useState("review-1");  return (    <NavigationList selectedId={selected} onSelect={setSelected} aria-label="Reviews">      <NavigationList.Item id="review-1">        <NavigationList.Title>feat: add toast component</NavigationList.Title>        <NavigationList.Meta>          <NavigationList.Badge variant="info">NEW</NavigationList.Badge>          <NavigationList.Subtitle>3 files changed</NavigationList.Subtitle>        </NavigationList.Meta>      </NavigationList.Item>      <NavigationList.Item id="review-2">        <NavigationList.Title>fix: dialog close on escape</NavigationList.Title>        <NavigationList.Meta>          <NavigationList.Badge variant="success">PASS</NavigationList.Badge>          <NavigationList.Subtitle>1 file changed</NavigationList.Subtitle>        </NavigationList.Meta>      </NavigationList.Item>      <NavigationList.Item id="review-3">        <NavigationList.Title>refactor: menu keyboard handling</NavigationList.Title>        <NavigationList.Status className="text-error">!</NavigationList.Status>        <NavigationList.Meta>          <NavigationList.Badge variant="error">FAIL</NavigationList.Badge>          <NavigationList.Subtitle>7 files changed</NavigationList.Subtitle>        </NavigationList.Meta>      </NavigationList.Item>      <NavigationList.Item id="review-4" disabled>        <NavigationList.Title>chore: update dependencies</NavigationList.Title>        <NavigationList.Meta>          <NavigationList.Subtitle>archived</NavigationList.Subtitle>        </NavigationList.Meta>      </NavigationList.Item>    </NavigationList>  );}

Examples

Density Variants

Preview

Controlled Selection

Preview

Progress Bars

Preview

Section Groups

Preview

Tree View

Preview

Indicator Variants

Preview

API Reference

NameTypeDefaultDescription
selectedIdstring | nullControlled selected item id.
defaultSelectedIdstring | nullnullInitial selected id for uncontrolled mode.
highlightedstring | nullControlled highlighted (focused) item id.
defaultHighlightedstring | nullnullInitial highlighted id for uncontrolled mode.
onSelect(id: string) => voidFired when an item is activated by click, Enter, or Space — including the already-selected item.
onEnter(id: string, event: KeyboardEvent) => voidFired when Enter activates an item. Receives the raw keyboard event for modifier-key handling.
onHighlightChange(id: string | null) => voidFired when the highlighted item changes.
onNavigationBoundaryReached(direction: "previous" | "next", event: KeyboardEvent, key: string) => voidFired when arrow navigation reaches the first/last item with wrap disabled, enabling cross-list navigation.
focusedbooleantrueWhen false, removes the active visual treatment from the selected/highlighted item (useful when focus is elsewhere).
wrapbooleantrueWhen true, arrow navigation wraps at list boundaries.
typeaheadbooleantrueEnable type-ahead character search to jump to matching items. Disable when every printable key the list's screen advertises belongs to an external hotkey layer.
indicator"bar" | "bar-thick" | "arrow" | "bracket""bar"Visual indicator style for the active/selected item. bar is a 4px rail at 40% fill, bar-thick an 8px rail at full fill; arrow and bracket mark the title glyph instead.
autoFocusbooleanfalseAuto-focus the list on mount.
aria-labelstringAccessible name for the list container.
childrenrequiredReactNodeNavigationList.Item children.
NameTypeDefaultDescription
idrequiredstringStable identifier matched against selectedId/highlighted.
density"compact" | "default" | "comfortable""default"Padding scale for the item content.
disabledbooleanfalseDisables activation; item is rendered with aria-disabled.
childrenrequiredReactNodeItem subparts (Title, Status, Meta, Badge, Subtitle).
NameTypeDefaultDescription
childrenrequiredReactNodePrimary label. Used as aria-labelledby for the item.
NameTypeDefaultDescription
childrenrequiredReactNodeTop-right status marker.
classNamestringMerged onto the marker. The slot is muted by default; pass a semantic tone (text-warning, text-error, text-success) at call sites where the status carries that meaning.
NameTypeDefaultDescription
childrenrequiredReactNodeContainer for inline metadata (badges, dates). Wired to aria-describedby.
NameTypeDefaultDescription
childrenrequiredReactNodeSecondary metadata text. Wired to aria-describedby.
NameTypeDefaultDescription
variant"success" | "warning" | "error" | "info" | "neutral""neutral"Semantic color token. Picks foreground, background, border, and dot color together for the Badge. See Badge for full prop reference.
size"sm" | "md" | "lg""sm"Padding and font-size / Badge size token.
childrenReactNodeBadge label.
NameTypeDefaultDescription
labelrequiredstringGroup header text.
expandedbooleanControlled expanded state.
defaultExpandedbooleantrueInitial expanded state for uncontrolled mode.
onExpandedChange(expanded: boolean) => voidFired when expanded state changes.
countnumberOptional count shown next to the label in section variant.
variant"tree" | "section""section"Visual treatment. "section" shows uppercase headers with counts, "tree" shows indented hierarchy with ASCII connectors.
headerIdstringStable header identity. Defaults to a per-instance generated value.
expandLabelstring"expand"Accessible action word appended to the header name while collapsed. Override it to localize the disclosure verb.
collapseLabelstring"collapse"Accessible action word appended to the header name while expanded. Override it to localize the disclosure verb.
childrenrequiredReactNodeNavigationList.Item or nested NavigationList.Group children.
NameTypeDefaultDescription
valuerequirednumberProgress percentage (0-100). Values are clamped to that range; non-finite values become zero.
variant"block" | "bar""block"Bar style. "block" uses █░ characters, "bar" uses [==-] characters.
widthnumber10Number of characters for the progress bar. Values are rounded down and capped at 200; negative and non-finite values become zero.
color"auto" | "success" | "warning" | "error" | "muted""auto"Color token. Auto selects color based on value thresholds.
showLabelbooleantrueShows percentage text after the bar.

Data attributes

AttributeApplies toValuesDescription
data-highlightedNavigationList.Itempresent when highlightedMarks the active descendant for keyboard and pointer styling.
data-selectedNavigationList.Itempresent when selectedMarks the currently selected item.
data-valueNavigationList.Itemitem idStable item id used by keyboard navigation.
data-indicatorNavigationList.Item indicator slot (non-tree items)"bar" | "bar-thick" | "arrow" | "bracket"Active indicator visual treatment. Emitted on the leading indicator cell inside the item, not on the option root.
data-stateNavigationList.Group header"open" | "closed"Group disclosure state changed by pointer or keyboard activation.

Accessibility

Keyboard Navigation

Arrow keys and their vim aliases j/k navigate between items with wrapping. Enter activates the highlighted item. Home and End jump to the first and last items.

KeyAction
ArrowUp / kMoves highlight to the previous enabled item.
ArrowDown / jMoves highlight to the next enabled item.
Home / EndMoves highlight to the first or last enabled item.
EnterActivates the highlighted item via onEnter or onSelect.
ArrowRightExpands the highlighted group header when it is collapsed.
ArrowLeftCollapses the highlighted group header when it is expanded.
Enter / SpaceToggles the highlighted group header.
Boundary ArrowUp / ArrowDown / k / jCalls onNavigationBoundaryReached with the pressed key when wrap is false and focus attempts to leave the list.

External @diffgazer/keys navigation

Preview

Notes

Current-location mark

The library spells "you are here" one way: a 2px left rail in --primary (registry/lib/marker-rail.ts). Full-bleed inversion is reserved for the TRANSIENT keyboard highlight; a row that is both the current location and the highlight keeps the inversion and flips its rail to --primary-foreground so the mark survives. The rail is reserved transparently in the resting state and pulled back by its own width, so a row's label never shifts horizontally when it becomes current — that anti-shift geometry is the contract, and it is why the rail costs 0px of label width at 375/390 where a full-bleed fill reads as a solid slab. NavigationList therefore draws a rail on the selected row instead of relying on the full-bleed fill alone, and the inner indicator no longer halves its opacity in `bar` mode.

Composition Contract

Use NavigationList.Item and its static parts as explicit children in the NavigationList JSX tree. Custom item UI belongs inside NavigationList.Item. Components that create items internally from an opaque wrapper are not part of the current public contract.

Density

density prop controls item padding — compact (6px), default (12px), or comfortable (20px).

Rich Items

NavigationList.Item supports compound parts: NavigationList.Title, NavigationList.Meta, NavigationList.Badge, NavigationList.Subtitle, and NavigationList.Status.

Built-in Keyboard API

NavigationList includes arrow-key navigation with the vim aliases j/k and exposes highlighted, onHighlightChange, onEnter, onNavigationBoundaryReached, autoFocus, focused, and onKeyDown for controlled highlight state or extra app-level shortcuts.

Group Expand/Collapse

Group headers participate in list navigation. ArrowRight expands a collapsed group, ArrowLeft collapses an expanded group, and Enter or Space toggles the highlighted group.

Source

Install via CLI: pnpm exec dgadd add ui/navigation-list. Keyboard hooks are included as standalone copies. For the full experience, use --integration keys.

Highlighted source loads after this disclosure opens. Browse the source repository.