Toolbar
A row of related controls that acts as a single tab stop, with arrow-key
navigation between items — formatting bars, editor controls. Built on Base UI
Toolbar.
Import
import { Toolbar } from "@photon-ai/pho-ui/components/toolbar";
Basic
Toolbar.Button, Link, and Input are the controls; Toolbar.Separator
divides groups. Every icon-only control needs an aria-label.
<Toolbar.Root aria-label="Formatting">
<Toolbar.Button aria-label="Bold">
<TextB weight="bold" />
</Toolbar.Button>
<Toolbar.Button aria-label="Italic">
<TextItalic />
</Toolbar.Button>
<Toolbar.Separator />
<Toolbar.Button aria-label="Align left">
<TextAlignLeft />
</Toolbar.Button>
</Toolbar.Root>
Pressable toggles
A plain Toolbar.Button fires an action; for a formatting control that stays
on until pressed again, render a Toggle through it with render. The button
keeps its place in the roving focus order while Toggle tracks the pressed
state (data-[pressed]). Group related toggles with Toolbar.Group.
import { Toggle } from "@photon-ai/pho-ui/components/toggle";
<Toolbar.Root aria-label="Formatting">
<Toolbar.Group>
<Toolbar.Button render={<Toggle aria-label="Bold" />}>
<TextB weight="bold" />
</Toolbar.Button>
<Toolbar.Button render={<Toggle aria-label="Italic" defaultPressed />}>
<TextItalic />
</Toolbar.Button>
</Toolbar.Group>
</Toolbar.Root>;
Inputs and links
Toolbar.Input and Toolbar.Link join the same roving focus order as buttons,
so a search field or a jump-out link stays reachable with the arrow keys.
<Toolbar.Root aria-label="Editor">
<Toolbar.Button aria-label="Undo">
<ArrowUUpLeft />
</Toolbar.Button>
<Toolbar.Separator />
<Toolbar.Input placeholder="Search" aria-label="Search" />
<Toolbar.Separator />
<Toolbar.Link href="/docs">Docs</Toolbar.Link>
</Toolbar.Root>
Anatomy
- Toolbar.Root — the container; one tab stop, arrow keys move between items
- Toolbar.Button — a button control (or a trigger for
Toggle/ a menu viarender) - Toolbar.Link — an
<a>control in the same focus order - Toolbar.Input — a text field in the same focus order
- Toolbar.Group — a sub-group of related controls
- Toolbar.Separator — a divider between groups (vertical in a horizontal toolbar)
Props
Toolbar.Root (renders a <div role="toolbar">)
- orientation —
horizontal·vertical(defaulthorizontal); sets the arrow-key axis - loopFocus —
boolean(defaulttrue) — wrap focus from the last item back to the first - disabled —
boolean(defaultfalse) — disable every control in the toolbar
Toolbar.Button (renders a <button>)
- disabled —
boolean(defaultfalse) — disable this control - focusableWhenDisabled —
boolean(defaulttrue) — keep it focusable while disabled so it’s still announced - render — render as another element (e.g. a
Toggleor menu trigger)
Toolbar.Separator (renders a <div role="separator">)
- orientation — defaults to the opposite of the toolbar’s orientation
Accessibility
Toolbar.Root renders role="toolbar" and reflects aria-orientation. Give it
an aria-label (or aria-labelledby) so its purpose is announced, and give
every icon-only Toolbar.Button an aria-label that names the action.
- Tab — move focus onto the toolbar (a single tab stop) and off it again
- Arrow keys — move between items — Left/Right when horizontal, Up/Down when vertical
Roving tabindex keeps only one item tabbable, so the whole toolbar is one stop
in the page’s tab order. Focus wraps at the ends while loopFocus is true.
Disabled buttons remain focusable by default (focusableWhenDisabled) so screen
readers still announce them.
Best practices
- Reach for a toolbar when several controls act on one target and should share a single tab stop — it keeps keyboard traversal quick.
- Label the toolbar (
aria-label) and every icon-only button so the purpose is announced. - Separate semantic groups (formatting vs alignment) with
Toolbar.Separatorinstead of relying on spacing alone. - Use
Toolbar.Buttonfor actions and aTogglerendered through it for on/off formatting states — don’t fake a pressed state with styling.