Keyboard accessible by default with semantic markup, visible focus treatment, and tokenized states. Add descriptive labels for icon-only or decorative usage.
Dependency notes
Check Installation for shared peers. This component keeps styling in Tailwind classes and the --zui-* token contract.
Navigation
Toolbar for related actions and formatting
Compose action buttons, formatting toggles, links, and groups in a toolbar with one keyboard entry point. Explore horizontal and vertical layouts, RTL navigation, wrapping, and 26 themeable appearances.
Ready to work.
Toolbar playground
Explore orientation, direction, wrapping, disabled actions, and every appearance. Try the keyboard directly in the preview.
Tab enters the toolbar once. Use Left/Right for horizontal layouts and Up/Down for vertical layouts. Home/End jump to the edges. Wrapping keeps controls visible on narrow screens; navigation follows DOM order across rows.
Tab enters once. Arrows move between available controls; Home and End jump to the edges.
Composition and keyboard behavior
Give every Toolbar an aria-label or aria-labelledby that describes its purpose. Use it for groups of at least three related controls. Label icon-only actions, and use aria-controls when the commands operate on a specific editor or canvas.
After hydration, the first available managed item is the only Tab stop. Arrow navigation and pointer focus update that entry point, so returning with Tab restores the last focused item. Home and End jump to the first and last available controls. Horizontal Left/Right follow the reading direction; vertical Up/Down stay the same in RTL. Set loop=false to stop at either edge.
ToolbarButton preserves native button props and defaults to type=button. ToolbarToggle supports pressed, defaultPressed, and onPressedChange; navigation moves focus without changing its state. ToolbarLink is a native anchor. ToolbarGroup groups related actions, and ToolbarSeparator is decorative unless decorative=false.
ToolbarItem composes one focusable child that forwards props and ref to its DOM control. It preserves the child’s handlers, merges classes and refs, and adds it to the toolbar’s navigation. Use native=false for links or controls that implement their own disabled behavior. Prevented events are respected. Popup content and nested toolbars keep their own navigation; perpendicular keys remain available to menu triggers.
Native disabled, aria-disabled, hidden, and inert managed items are skipped. Root disabled disables the complete toolbar, and disabled links cannot activate. Dynamic additions, removals, and visibility changes update the Tab stop. Native text editing keys remain with the input; prefer keeping text inputs outside the toolbar’s arrow-navigation sequence.
Wrapping is enabled by default for narrow screens. With wrap=false, provide a scrolling container when controls exceed the available width. Arrow navigation follows DOM order across wrapped rows. The static entry has no animation dependency and respects reduced motion for its CSS color transitions.
decorativeDecorative by default; false exposes separator semantics and orientation.
boolean | undefined
none
Inherited HTML props
Prop
Type
Default
className
string | undefined
none
id
string | undefined
none
onClick
MouseEventHandler<HTMLDivElement> | undefined
none
style
CSSProperties | undefined
none
title
string | undefined
none
ToolbarToggle
ToolbarToggleProps
Behavior
Prop
Type
Default
defaultPressedInitial uncontrolled pressed state. Defaults to false.
boolean | undefined
none
onPressedChange
((pressed: boolean) => void) | undefined
none
pressedControlled pressed state. Arrow navigation never changes it.
boolean | undefined
none
Inherited HTML props
Prop
Type
Default
children
ReactNode
none
className
string | undefined
none
id
string | undefined
none
onClick
MouseEventHandler<HTMLButtonElement> | undefined
none
style
CSSProperties | undefined
none
title
string | undefined
none
Installation and theme overrides
pnpm add @zentauri-ui/zentauri-components
# Or vendor the Toolbar and the Button used in the composition recipe:pnpm dlx @zentauri-ui/zentauri-components add toolbar buttons
Include the package in Tailwind v4 scan scope. Adjust the source path for your stylesheet; vendored files also need scanning.
Apply className="editor-toolbar" to Toolbar. Its appearance provides the surface and inherited foreground; each item uses Toolbar spacing, hover, pressed, and focus tokens. A composed Button can keep its own appearance via class merging.
Groups related commands with button, toggle, link, item, group, and separator primitives. Supports 26 appearances, three sizes, and paired theme tokens.
Common use cases
Editor formatting and document actions.
Canvas tools and dashboard commands.
Related links and actions in workspaces.
Accessibility
Name each toolbar, label icon-only controls, and group at least three related actions. Native buttons and links preserve activation semantics, while roving tabindex reduces repeated Tab stops. Prevented events and nested widget navigation are respected.
FAQ
How does keyboard navigation work?
After hydration, Tab enters at the first available item, then returns to the most recently focused item. Horizontal arrows follow the reading direction; vertical toolbars use Up/Down. Home and End jump to the edges, and loop defaults to true.
Can I compose an existing Button?
Yes. Wrap a single focusable control in ToolbarItem. It must forward props and ref to its DOM element. For links or non-native widgets, set native=false and implement their disabled semantics.
Do arrows activate toggle buttons?
No. Arrows only move focus. Click, Enter, or Space activate ToolbarToggle. Use pressed and onPressedChange for controlled state, or defaultPressed for local state.
How does Toolbar handle disabled and hidden items?
Disabled, aria-disabled, hidden, and inert managed items are skipped. Root disabled disables all managed actions and blocks link activation. Changes to items update the keyboard entry point.
What happens on mobile?
Controls wrap by default. Set wrap=false and provide a scrolling container when you want a single row. Navigation follows DOM order across rows.