Skip to content

Toolbar

In review
Live · web

Formatting: toggles stay pressed · Tab in, Arrow keys move, the ⋯ overflows

Contextual: a selection action bar (count · actions · close)

Primary actions: labelled buttons + icon buttons, one filled primary

Responsive: when space is tight, extra items fold into the overflow

Vertical: Up / Down move focus

Wrapping: a disabled control stays legible (skipped by arrows)

Marks: bold · align left · last action none. Pointer + keyboard both work; the island is live.

// Web: React, consuming @dizyx/nockerl-tokens. There is NO bespoke Toolbar*NockerlButton
// family: a toolbar SLOTS the shipped primitives (NockerlIconButton / NockerlButton / NockerlDivider /
// NockerlSegmentedControl) inside a role="toolbar" container that owns the roving tabindex.
// Toggles use the primitives' `pressed` prop (soft-cyan wash + aria-pressed).
import { NockerlIconButton, NockerlButton, NockerlDivider, NockerlSegmentedControl } from '@dizyx/nockerl-react';
export function FormattingToolbar() {
const [marks, setMarks] = useState({ bold: true, italic: false });
const [view, setView] = useState('split');
// reg / tab / onKey come from the toolbar container's roving-tabindex hook: each
// child registers its DOM node (NockerlIconButton/NockerlButton forward the ref), is the sole tab
// stop when tab === idx, and forwards Arrow/Home/End to the container. See ToolbarDemo.
return (
<Toolbar label="Text formatting">
{({ reg, tab, onKey }) => (
<>
<span className="toolbar-group">
<NockerlIconButton icon={<BoldIcon />} label="Bold" pressed={marks.bold}
ref={(el) => reg(0, el)} tabIndex={tab === 0 ? 0 : -1} onKeyDown={(e) => onKey(e, 0)}
onClick={() => setMarks((m) => ({ ...m, bold: !m.bold }))} />
<NockerlIconButton icon={<ItalicIcon />} label="Italic" pressed={marks.italic}
ref={(el) => reg(1, el)} tabIndex={tab === 1 ? 0 : -1} onKeyDown={(e) => onKey(e, 1)}
onClick={() => setMarks((m) => ({ ...m, italic: !m.italic }))} />
</span>
<NockerlDivider orientation="vertical" />
{/* a mutually-exclusive group → the real NockerlSegmentedControl (single-select). NockerlIcon
keys come from SEGMENT_ICONS: split · list · unified · grid · board · sun · moon · auto. */}
<NockerlSegmentedControl label="View" value={view} onChange={setView} size="sm"
segments={[{ value: 'split', icon: 'split' }, { value: 'list', icon: 'list' }, { value: 'grid', icon: 'grid' }]} />
<NockerlDivider orientation="vertical" />
{/* one filled primary (labelled NockerlButton); the ⋯ overflow trigger is a plain NockerlIconButton */}
<NockerlButton variant="primary" size="sm" leadingIcon={<PlusIcon />} text="New session"
ref={(el) => reg(2, el)} tabIndex={tab === 2 ? 0 : -1} onKeyDown={(e) => onKey(e, 2)} onClick={createSession} />
<NockerlIconButton variant="plain" icon={<KebabIcon />} label="More actions"
ref={(el) => reg(3, el)} tabIndex={tab === 3 ? 0 : -1} onKeyDown={(e) => onKey(e, 3)}
aria-haspopup="menu" aria-expanded={open} onClick={() => setOpen(true)} />
</>
)}
</Toolbar>
);
}
PropTypeDefaultDescription
label *stringAccessible name for the strip (wired to role="toolbar" via aria-label). Required: a toolbar is one focus group and needs a name.
orientation'horizontal' | 'vertical''horizontal'Layout axis. Arrow keys follow it: Left/Right when horizontal, Up/Down when vertical.
children(roving) => ReactNodeThe container SLOTS the real primitives: IconButton, Button, Divider, SegmentedControl, and the overflow ⋯ (a plain IconButton + a Menu). NOT a bespoke Toolbar*Button family. The render-prop hands each child its roving plumbing (reg / tab / onKey).
IconButton.pressed / Button.pressedbooleanTOGGLE mode on the real primitive: renders the soft-cyan selection wash and sets aria-pressed. Use for sticky formatting marks (bold/italic) or a labelled toggle (Wrap). Omit for a plain command control.
Button.variant'primary' | 'ghost' | …'primary'Labelled toolbar buttons are the Button primitive. Use primary for the one filled CTA (max one per strip), ghost for plain labelled actions (Filter / Share). A destructive icon tints via style={{ color: 'var(--color-status-error)' }} (IconButton has no danger idiom).
roving = { reg, tab, onKey }{ reg; tab; onKey }Wire each slotted primitive with ref={(el) => reg(idx, el)}, tabIndex={tab === idx ? 0 : -1}, onKeyDown={(e) => onKey(idx)}. Button + IconButton forward the ref and spread these native attrs, so the container keeps exactly one child tabbable and Arrow/Home/End move focus across controls.
SegmentedControlprimitiveA mutually-exclusive segment group inside the toolbar (e.g. alignment) is the real SegmentedControl (segments / value / onChange, single-select). Its own role="radiogroup" + roving tabindex nests inside the strip.
Overflow ⋯ itemsOverflowItem[]The ⋯ trigger is a plain IconButton opening a Menu of folded-away actions (icon + label, optional checked / danger). Use to collapse low-priority or space-tight controls.