Vertical navigation rail with sections, nested items, badges, and an optional collapsible width.
- Sections with optional titles and nested items up to two levels deep
- Controlled and uncontrolled collapse with width swap and toggle button
- Tone-aware badges per item and surface-coherent borders/typography
- aside-style nav landmark with aria-current and aria-expanded toggle
- Status
- stable
- Category
- navigation
- Since
- v1.9.0
import { PixelSidebar } from '@pxlkit/ui-kit';
| Prop | Type | Default | Description |
|---|
collapsible | boolean | — | Shows the collapse toggle in the header row. |
defaultCollapsed | boolean | — | Initial collapsed state while uncontrolled. |
collapsed | boolean | — | Collapsed state; leave unset for an uncontrolled rail. |
onCollapsedChange | (next: boolean) => void | — | Called with the collapsed state the toggle asks for. |
sections* (required) | PixelSidebarSectionProps[] | — | The sections, in order. |
header | React.ReactNode | — | Header content beside the toggle (hidden while collapsed). |
footer | React.ReactNode | — | Footer row content. |
surface | 'pixel' | 'linear' | — | Surface override; defaults to the nearest provider. |
Also takes the native attributes and event handlers of its element (HTMLAttributes<HTMLElement>).
ref points to its element (HTMLElement).
WCAG target: 2.1 AA
nav landmarkaria-currentaria-expanded togglebadge aria-label
Collapse toggle uses aria-expanded; collapsed items expose their label via aria-label and title.
Keyboard
| Key | Action | When |
|---|
| Tab | Moves focus through items and the collapse toggle | — |
| Enter / Space | Activates the focused item or toggle | — |
import { PixelSidebar } from '@pxlkit/ui-kit';
const sections = [
{
title: 'Workspace',
items: [
{ id: 'dashboard', label: 'Dashboard', active: true },
{ id: 'projects', label: 'Projects', badge: { label: '4', tone: 'cyan' as const } },
{ id: 'tasks', label: 'Tasks' },
],
},
{
title: 'Account',
items: [
{ id: 'settings', label: 'Settings' },
{ id: 'billing', label: 'Billing', badge: { label: 'NEW', tone: 'green' as const } },
],
},
];
export function Default() {
return (
<div style={{ height: 320 }}>
<PixelSidebar sections={sections} header={<span className="text-xs text-retro-text">pxlkit</span>} />
</div>
);
}
Examples
Default
import { PixelSidebar } from '@pxlkit/ui-kit';
const sections = [
{
title: 'Workspace',
items: [
{ id: 'dashboard', label: 'Dashboard', active: true },
{ id: 'projects', label: 'Projects', badge: { label: '4', tone: 'cyan' as const } },
{ id: 'tasks', label: 'Tasks' },
],
},
{
title: 'Account',
items: [
{ id: 'settings', label: 'Settings' },
{ id: 'billing', label: 'Billing', badge: { label: 'NEW', tone: 'green' as const } },
],
},
];
export function Default() {
return (
<div style={{ height: 320 }}>
<PixelSidebar sections={sections} header={<span className="text-xs text-retro-text">pxlkit</span>} />
</div>
);
}
Collapsible
import React from 'react';
import { PixelSidebar } from '@pxlkit/ui-kit';
const sections = [
{
title: 'Workspace',
items: [
{ id: 'dashboard', label: 'Dashboard', active: true },
{ id: 'projects', label: 'Projects', badge: { label: '4', tone: 'cyan' as const } },
{ id: 'tasks', label: 'Tasks' },
],
},
{
title: 'Account',
items: [
{ id: 'settings', label: 'Settings' },
{ id: 'billing', label: 'Billing', badge: { label: 'NEW', tone: 'green' as const } },
],
},
];
export function Collapsible() {
const [collapsed, setCollapsed] = React.useState(false);
return (
<div style={{ height: 320 }}>
<PixelSidebar
collapsible
collapsed={collapsed}
onCollapsedChange={setCollapsed}
sections={sections}
header={<span className="text-xs text-retro-text">pxlkit</span>}
/>
</div>
);
}
Nested items
import { PixelSidebar } from '@pxlkit/ui-kit';
export function Nested() {
const nestedSections = [
{
title: 'Library',
items: [
{
id: 'components',
label: 'Components',
active: true,
nested: [
{ id: 'buttons', label: 'Buttons' },
{ id: 'forms', label: 'Forms' },
{ id: 'navigation', label: 'Navigation' },
],
},
{ id: 'tokens', label: 'Tokens' },
],
},
];
return (
<div style={{ height: 320 }}>
<PixelSidebar sections={nestedSections} />
</div>
);
}