NavList
NavList is a vertical navigation list for sidebars: grouped link entries with optional headings, trailing badges and a highlighted current entry. The sidebar of this documentation is a NavList.
import { NavList } from 'jarvis-react-ui';
Groups and badges
groups is an array of blocks. Each group has an optional label (omit it for an ungrouped block
at the top) and its items. An item needs a unique id, a label and an href; badge adds
trailing content such as a Pill. The entry whose id equals activeId is
highlighted.
import { NavList, Pill } from 'jarvis-react-ui';
import type { NavListGroup } from 'jarvis-react-ui';
const GROUPS: NavListGroup[] = [
{ items: [{ id: 'overview', label: 'Overview', href: '#overview' }] },
{
label: 'Systems',
items: [
{ id: 'power', label: 'Power', href: '#power' },
{ id: 'comms', label: 'Comms', href: '#comms', badge: <Pill variant="warn">2</Pill> },
{ id: 'sensors', label: 'Sensors', href: '#sensors' },
],
},
{
label: 'Labs',
items: [
{
id: 'orb',
label: 'Orb studio',
href: '#orb',
badge: <Pill variant="info">new</Pill>,
},
],
},
];
export default function Groups() {
return <NavList className="w-[220px]" aria-label="Systems" groups={GROUPS} activeId="power" />;
}
Client-side routing
Entries are real <a href> links, so the list works without JavaScript and supports opening in a
new tab. onItemClick runs before the browser follows the link; call event.preventDefault() to
handle navigation yourself and update activeId.
import { useState } from 'react';
import { NavList } from 'jarvis-react-ui';
import type { NavListGroup } from 'jarvis-react-ui';
const GROUPS: NavListGroup[] = [
{
label: 'Mission',
items: [
{ id: 'brief', label: 'Briefing', href: '#brief' },
{ id: 'route', label: 'Route', href: '#route' },
{ id: 'debrief', label: 'Debrief', href: '#debrief' },
],
},
];
export default function ClientSide() {
const [active, setActive] = useState('brief');
return (
<div className="flex items-start gap-6">
<NavList
className="w-[200px]"
aria-label="Mission"
groups={GROUPS}
activeId={active}
onItemClick={(item, e) => {
e.preventDefault();
setActive(item.id);
}}
/>
<p className="m-0 font-mono text-[11px] text-text-secondary">
Active: <span className="text-accent">{active}</span>
</p>
</div>
);
}
With a router, derive activeId from the current location and navigate in onItemClick. Let
modified clicks (Ctrl, Cmd, Shift, middle button) through so "open in new tab" keeps working. This
is how the sidebar of this site is wired up with React Router:
import { useLocation, useNavigate } from 'react-router';
import { NavList } from 'jarvis-react-ui';
import type { NavListGroup } from 'jarvis-react-ui';
const GROUPS: NavListGroup[] = [
{
label: 'Components',
items: [
{ id: 'button', label: 'Button', href: '/components/button' },
{ id: 'tabs', label: 'Tabs', href: '/components/tabs' },
],
},
];
export function Sidebar() {
const navigate = useNavigate();
const { pathname } = useLocation();
return (
<NavList
aria-label="Documentation"
groups={GROUPS}
activeId={pathname.split('/').pop()}
onItemClick={(item, e) => {
if (e.metaKey || e.ctrlKey || e.shiftKey || e.button !== 0) return;
e.preventDefault();
void navigate(item.href);
}}
/>
);
}
Sound
Hovering an entry plays hover_button and clicking plays click through the nearest
SfxProvider. Without a provider the list stays silent. See Sound effects.
Accessibility
- Renders a
<nav>landmark named byaria-label(default "Navigation"). Give each NavList on a page a distinct name. - Every group is a
<ul>list of<li>items, so screen readers announce the number of entries. - The active entry gets
aria-current="page"in addition to the visual highlight. - Entries are native links: Tab moves between them, Enter follows them, and the focus outline is visible on keyboard focus.
- Group labels are plain text, not headings, so they do not appear in the heading outline.
API
| Prop | Type | Default | Description |
|---|---|---|---|
groups (required) |
NavListGroup[] |
Navigation groups in display order. | |
activeId |
string |
Id of the current entry; it is highlighted and gets aria-current="page". |
|
aria-label |
string |
Navigation |
Accessible name of the <nav> landmark. |
className |
string |
Additional class names for the root <nav> element. |
|
onItemClick |
((item: NavListItem, event: MouseEvent<HTMLAnchorElement, MouseEvent>) => void) |
Called when an entry is clicked, before the browser follows the link. Call event.preventDefault() to handle navigation yourself (e.g. with a client-side router). |
Type NavListGroup
A titled block of NavList entries.
| Field | Type | Default | Description |
|---|---|---|---|
items (required) |
NavListItem[] |
Entries in this group. | |
label |
string |
Group heading. Omit for an ungrouped block at the top. |
Type NavListItem
A single link entry of a NavList.
| Field | Type | Default | Description |
|---|---|---|---|
href (required) |
string |
Link target. Entries are rendered as anchors so they work without JavaScript. | |
id (required) |
string |
Unique id of the entry; compared with activeId. |
|
label (required) |
ReactNode |
Visible text of the entry. | |
badge |
ReactNode |
Optional trailing badge, e.g. a Pill saying "new". |