Skip to documentation content

Toolbar

Group compact controls that act on the same surface or object.

toolbar-demo

Overview

Use Toolbar to group compact controls that all act on the same surface or object: a text formatting bar, canvas tools, table density controls, a media transport. The grouping is not just visual — it changes keyboard behavior, letting people move through the whole set with arrow keys instead of tabbing through every control.

Toolbars are for actions on content that is already visible. They are not site navigation and not a form; see Don't do this.

Anatomy

A toolbar root holds buttons, links, inputs, toggle groups, selects, separators, and menus. ToolbarButton, ToolbarLink, and ToolbarInput participate in the toolbar's roving keyboard navigation; ToolbarGroup marks a related subset such as an alignment trio; ToolbarSeparator divides groups visually and follows the root's orientation.

Compose with existing primitives through the render prop — the full demo wires Toggle, Select, Button, and Tooltip into toolbar parts so each control keeps its own behavior while joining the roving focus model.

Behavior

Keyboard model. The toolbar is one Tab stop. Once inside, ArrowLeft/ArrowRight (or Up/Down when vertical) move between controls and wrap around by default; set loopFocus={false} to stop at the ends. Note that Home/End are not wired up in toolbars, unlike tabs. An input inside the toolbar keeps normal caret movement: ArrowLeft/ArrowRight move through its text first, and only when the caret is already at the edge do they move focus to the next control. Holding Shift or having a selection always keeps the keys native.

Disabled state. Set disabled on the root to disable everything at once. Individual ToolbarButtons accept focusableWhenDisabled so a disabled control stays reachable and announced while ignoring activation — useful when you need to explain why something is off.

Orientation. Horizontal is right for bars above or below content. Vertical suits rails beside a canvas, but only when labels make the direction obvious.

Accessibility

The root renders <div role="toolbar" aria-orientation="...">. Give it an accessible name when no nearby heading does: pass aria-label="Text formatting" (or aria-labelledby) on Toolbar.

Icon-only controls have no text, so give each one an aria-label; the examples pair them with Tooltips whose text matches. Toggles must expose their pressed state persistently — compose with Toggle rather than swapping icons, because "pressed" is information, not decoration. Separators never receive focus.

Focus remains visible on every control via focus-visible rings. Colors come from theme tokens, so hover, active, and disabled states adapt to dark mode automatically. In right-to-left layouts the arrow-key direction mirrors automatically through the direction provider.

Long labels do not wrap inside toolbar buttons; keep command labels to one or two words and push less common commands into a menu when the bar crowds.

Installation

npx honestui@latest add toolbar

Usage

import { Button } from "@/components/ui/button";
import { Toggle } from "@/components/ui/toggle";
import {
  Toolbar,
  ToolbarButton,
  ToolbarGroup,
  ToolbarInput,
  ToolbarLink,
  ToolbarSeparator,
} from "@/components/ui/toolbar";
<Toolbar aria-label="Text formatting">
  <ToolbarGroup>
    <ToolbarButton render={<Toggle />}>Bold</ToolbarButton>
    <ToolbarButton render={<Toggle />}>Underline</ToolbarButton>
  </ToolbarGroup>
  <ToolbarSeparator />
  <ToolbarButton render={<Button />}>Save document</ToolbarButton>
</Toolbar>

Wrap icon-only buttons with Tooltip to surface their names on hover while aria-label carries the accessible name everywhere else.

Don't do this

Faking a toolbar with plain buttons

// Bad
<div className="flex gap-1">
  <Button size="icon" onClick={select}><MousePointer2Icon /></Button>
  <Button size="icon" onClick={move}><MoveIcon /></Button>
  <Button size="icon" onClick={draw}><PenLineIcon /></Button>
</div>
// Good
<Toolbar aria-label="Canvas tools">
  <ToolbarButton render={<Button variant="ghost" size="icon" />} aria-label="Select">
    <MousePointer2Icon />
  </ToolbarButton>
  ...
</Toolbar>

The <div> version makes every button its own Tab stop, ignores arrow keys entirely, and announces nothing about the grouping. Keyboard users pay one Tab press per control instead of one per toolbar, and screen-reader users lose the "toolbar" landmark that signals these commands act on adjacent content.

Icon-only buttons without names

// Bad
<ToolbarButton render={<Button variant="ghost" size="icon" />}>
  <PlayIcon />
</ToolbarButton>
// Good
<ToolbarButton render={<Button variant="ghost" size="icon" />} aria-label="Play">
  <PlayIcon />
</ToolbarButton>

An unnamed icon button is announced as bare "button" and is invisible to voice control. Tooltips help sighted mouse users but do nothing on touch screens or for screen readers — aria-label is the fix, and the tooltip text should repeat it verbatim.

Using a toolbar for page navigation

// Bad
<Toolbar>
  <ToolbarLink href="/docs">Docs</ToolbarLink>
  <ToolbarLink href="/pricing">Pricing</ToolbarLink>
</Toolbar>
// Good
<nav aria-label="Main">
  <Link href="/docs">Docs</Link>
  <Link href="/pricing">Pricing</Link>
</nav>

A toolbar means "these controls edit the thing next to them." Primary destinations are not edits on anything; burying them in a toolbar hides them from the navigation landmark screen readers use to orient, and implies the wrong mental model even for sighted users.

Examples

Canvas controls

Tool selection grouped with a separator before the draw action — the classic editor rail pattern.

toolbar-canvas-controls

Media controls

Transport buttons where every control needs both a tooltip and an aria-label.

toolbar-media-controls

Density controls

Table or list density toggles with a reset action separated from the group.

toolbar-density-controls

API reference

All parts forward their matching Base UI Toolbar props.

PropComponentValuesDefault
orientationToolbar"horizontal", "vertical""horizontal"
loopFocusToolbarbooleantrue
disabledToolbarbooleanfalse
focusableWhenDisabledToolbarButtonboolean—

The root renders <div role="toolbar" aria-orientation="...">. ToolbarButton, ToolbarLink, and ToolbarInput join the roving focus set; other children render normally without participating in arrow-key movement. ToolbarSeparator mirrors the root's orientation automatically. Unlike tabs, Home/End keys are not enabled inside toolbars — arrows wrap instead.

See the Base UI Toolbar API for keyboard behavior and root props.