# Toolbar

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

Source: https://www.honestui.com/docs/components/toolbar

```tsx
import {
  TextAlignCenter as AlignCenterIcon,
  TextAlignStart as AlignLeftIcon,
  TextAlignEnd as AlignRightIcon,
  DollarSign as DollarSignIcon,
  Percent as PercentIcon,
} from "honestui/icons";

import { Button } from "@/components/honest-ui/ui/button";
import {
  Select,
  SelectItem,
  SelectPopup,
  SelectTrigger,
  SelectValue,
} from "@/components/honest-ui/ui/select";
import { Toggle, ToggleGroup } from "@/components/honest-ui/ui/toggle";
import {
  Toolbar,
  ToolbarButton,
  ToolbarGroup,
  ToolbarInput,
  ToolbarLink,
  ToolbarSeparator,
} from "@/components/honest-ui/ui/toolbar";
import {
  Tooltip,
  TooltipPopup,
  TooltipProvider,
  TooltipTrigger,
} from "@/components/honest-ui/ui/tooltip";

const items = [
  { label: "Helvetica", value: "helvetica" },
  { label: "Arial", value: "arial" },
  { label: "Times New Roman", value: "times-new-roman" },
];

export function ToolbarDemo() {
  return (
    <TooltipProvider>
      <Toolbar>
        <ToggleGroup className="border-none p-0" defaultValue={["left"]}>
          <Tooltip>
            <TooltipTrigger
              render={
                <ToolbarButton
                  render={<Toggle value="left" />}
                  aria-label="Align left"
                >
                  <AlignLeftIcon />
                </ToolbarButton>
              }
            />
            <TooltipPopup sideOffset={8}>Align left</TooltipPopup>
          </Tooltip>
          <Tooltip>
            <TooltipTrigger
              render={
                <ToolbarButton
                  render={<Toggle value="center" aria-label="Toggle center" />}
                  aria-label="Align center"
                >
                  <AlignCenterIcon />
                </ToolbarButton>
              }
            />
            <TooltipPopup sideOffset={8}>Align center</TooltipPopup>
          </Tooltip>
          <Tooltip>
            <TooltipTrigger
              render={
                <ToolbarButton
                  render={<Toggle value="right" aria-label="Toggle right" />}
                  aria-label="Align right"
                >
                  <AlignRightIcon />
                </ToolbarButton>
              }
            />
            <TooltipPopup sideOffset={8}>Align right</TooltipPopup>
          </Tooltip>
        </ToggleGroup>
        <ToolbarSeparator />
        <ToolbarInput
          aria-label="Find in document"
          className="h-8 min-w-0 rounded-md border border-input bg-transparent px-2 text-sm outline-none focus-visible:ring-2 focus-visible:ring-ring"
          placeholder="Find"
          type="search"
        />
        <ToolbarLink
          className="rounded-md px-2 py-1.5 text-sm text-muted-foreground outline-none hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring"
          href="/docs/components/toolbar"
        >
          Help
        </ToolbarLink>
        <ToolbarSeparator />
        <ToolbarGroup>
          <Tooltip>
            <TooltipTrigger
              render={
                <ToolbarButton
                  render={<Button variant="ghost" size="icon" />}
                  aria-label="Format as currency"
                >
                  <DollarSignIcon />
                </ToolbarButton>
              }
            />
            <TooltipPopup sideOffset={8}>Format as currency</TooltipPopup>
          </Tooltip>
          <Tooltip>
            <TooltipTrigger
              render={
                <ToolbarButton
                  render={<Button variant="ghost" size="icon" />}
                  aria-label="Format as percent"
                >
                  <PercentIcon />
                </ToolbarButton>
              }
            />
            <TooltipPopup sideOffset={8}>Format as percent</TooltipPopup>
          </Tooltip>
        </ToolbarGroup>
        <ToolbarSeparator />
        <ToolbarGroup>
          <Select items={items} defaultValue="helvetica">
            <Tooltip>
              <TooltipTrigger
                render={
                  <ToolbarButton
                    render={
                      <SelectTrigger>
                        <SelectValue />
                      </SelectTrigger>
                    }
                  />
                }
              />
              <TooltipPopup sideOffset={8}>
                Select a different font
              </TooltipPopup>
            </Tooltip>
            <SelectPopup>
              {items.map(({ label, value }) => (
                <SelectItem key={value} value={value}>
                  {label}
                </SelectItem>
              ))}
            </SelectPopup>
          </Select>
        </ToolbarGroup>
        <ToolbarSeparator />
        <ToolbarGroup>
          <ToolbarButton render={<Button />}>Save</ToolbarButton>
        </ToolbarGroup>
      </Toolbar>
    </TooltipProvider>
  );
}

```

## Overview [#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](#dont-do-this).

## Anatomy [#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 [#behavior]

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

**Disabled state.** Set `disabled` on the root to disable everything at once. Individual `ToolbarButton`s 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 [#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 [#installation]


  

  
    <CliBlock commands="[&#x22;toolbar&#x22;]" />
  

  
    
      
        Install the following dependencies:
      

      ```bash
      npm install @base-ui-components/react
      ```

      
        Copy and paste the following code into your project.
      

      ### components/ui/toolbar.tsx

```tsx
"use client"

import { Toolbar as ToolbarPrimitive } from "@base-ui-components/react/toolbar"

import { cn } from "@/lib/utils"

function Toolbar({ className, ...props }: ToolbarPrimitive.Root.Props) {
  return (
    <ToolbarPrimitive.Root
      data-slot="toolbar"
      className={cn(
        "flex items-center gap-[var(--hui-space-1)] overflow-clip rounded-[var(--hui-radius-2)] border-[0.5px] border-[var(--hui-color-border-base-primary)] p-[var(--hui-space-1)] data-[orientation=vertical]:flex-col data-[orientation=vertical]:[&_[data-slot=toolbar-button]]:w-full data-[orientation=vertical]:[&_[data-slot=toolbar-group]]:flex-col [&>*]:border-0",
        className
      )}
      {...props}
    />
  )
}

function ToolbarButton({ className, ...props }: ToolbarPrimitive.Button.Props) {
  return (
    <ToolbarPrimitive.Button
      data-slot="toolbar-button"
      className={cn(className)}
      {...props}
    />
  )
}

function ToolbarLink({ className, ...props }: ToolbarPrimitive.Link.Props) {
  return (
    <ToolbarPrimitive.Link
      data-slot="toolbar-link"
      className={cn(className)}
      {...props}
    />
  )
}

function ToolbarInput({ className, ...props }: ToolbarPrimitive.Input.Props) {
  return (
    <ToolbarPrimitive.Input
      data-slot="toolbar-input"
      className={cn(className)}
      {...props}
    />
  )
}

function ToolbarGroup({ className, ...props }: ToolbarPrimitive.Group.Props) {
  return (
    <ToolbarPrimitive.Group
      data-slot="toolbar-group"
      className={cn(
        "flex items-center gap-[var(--hui-space-1)] bg-transparent p-0 [&>*]:border-0",
        className
      )}
      {...props}
    />
  )
}

function ToolbarSeparator({
  className,
  ...props
}: ToolbarPrimitive.Separator.Props) {
  return (
    <ToolbarPrimitive.Separator
      data-slot="toolbar-separator"
      className={cn(
        "shrink-0 bg-[var(--hui-color-border-base-primary)] data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-[calc(100%-var(--hui-space-3))] data-[orientation=vertical]:h-[var(--hui-space-5)] data-[orientation=vertical]:w-px",
        className
      )}
      {...props}
    />
  )
}

export {
  Toolbar,
  ToolbarGroup,
  ToolbarSeparator,
  ToolbarButton,
  ToolbarLink,
  ToolbarInput,
}

```

      
        Update the import paths to match your project setup.
      
    
  


## Usage [#usage]

```tsx
import { Button } from "@/components/ui/button";
import { Toggle } from "@/components/ui/toggle";
import {
  Toolbar,
  ToolbarButton,
  ToolbarGroup,
  ToolbarInput,
  ToolbarLink,
  ToolbarSeparator,
} from "@/components/ui/toolbar";
```

```tsx
<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 [#dont-do-this]

### Faking a toolbar with plain buttons [#faking-a-toolbar-with-plain-buttons]

```tsx
// 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>
```

```tsx
// 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 <kbd>Tab</kbd> 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 [#icon-only-buttons-without-names]

```tsx
// Bad
<ToolbarButton render={<Button variant="ghost" size="icon" />}>
  <PlayIcon />
</ToolbarButton>
```

```tsx
// 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 [#using-a-toolbar-for-page-navigation]

```tsx
// Bad
<Toolbar>
  <ToolbarLink href="/docs">Docs</ToolbarLink>
  <ToolbarLink href="/pricing">Pricing</ToolbarLink>
</Toolbar>
```

```tsx
// Good
<nav aria-label="Main">
  [Docs](/docs)
  [Pricing](/pricing)
</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 [#examples]

### Canvas controls [#canvas-controls]

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

```tsx
import { MousePointer2 as MousePointer2Icon, Move as MoveIcon, PenLine as PenLineIcon } from "honestui/icons"

import { Button } from "@/components/honest-ui/ui/button"
import { Toolbar, ToolbarButton, ToolbarGroup, ToolbarSeparator } from "@/components/honest-ui/ui/toolbar"

export function ToolbarCanvasControls() {
  return (
    <Toolbar>
      <ToolbarGroup>
        <ToolbarButton render={<Button variant="ghost" size="icon" />} aria-label="Select"><MousePointer2Icon /></ToolbarButton>
        <ToolbarButton render={<Button variant="ghost" size="icon" />} aria-label="Move"><MoveIcon /></ToolbarButton>
      </ToolbarGroup>
      <ToolbarSeparator />
      <ToolbarButton render={<Button variant="ghost" size="icon" />} aria-label="Draw"><PenLineIcon /></ToolbarButton>
    </Toolbar>
  )
}

```

### Media controls [#media-controls]

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

```tsx
import { Pause as PauseIcon, Play as PlayIcon, SkipForward as SkipForwardIcon } from "honestui/icons"

import { Button } from "@/components/honest-ui/ui/button"
import { Toolbar, ToolbarButton, ToolbarGroup } from "@/components/honest-ui/ui/toolbar"

export function ToolbarMediaControls() {
  return (
    <Toolbar>
      <ToolbarGroup>
        <ToolbarButton render={<Button variant="ghost" size="icon" />} aria-label="Play"><PlayIcon /></ToolbarButton>
        <ToolbarButton render={<Button variant="ghost" size="icon" />} aria-label="Pause"><PauseIcon /></ToolbarButton>
        <ToolbarButton render={<Button variant="ghost" size="icon" />} aria-label="Skip"><SkipForwardIcon /></ToolbarButton>
      </ToolbarGroup>
    </Toolbar>
  )
}

```

### Density controls [#density-controls]

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

```tsx
import { Rows3 as Rows3Icon, Rows4 as Rows4Icon } from "honestui/icons"

import { Button } from "@/components/honest-ui/ui/button"
import { Toolbar, ToolbarButton, ToolbarGroup, ToolbarSeparator } from "@/components/honest-ui/ui/toolbar"

export function ToolbarDensityControls() {
  return (
    <Toolbar>
      <ToolbarGroup>
        <ToolbarButton render={<Button variant="ghost" size="icon" />} aria-label="Comfortable"><Rows4Icon /></ToolbarButton>
        <ToolbarButton render={<Button variant="ghost" size="icon" />} aria-label="Compact"><Rows3Icon /></ToolbarButton>
      </ToolbarGroup>
      <ToolbarSeparator />
      <ToolbarButton render={<Button variant="ghost" />}>Reset</ToolbarButton>
    </Toolbar>
  )
}

```

## API reference [#api-reference]

All parts forward their matching Base UI Toolbar props.

| Prop                    | Component       | Values                       | Default        |
| ----------------------- | --------------- | ---------------------------- | -------------- |
| `orientation`           | `Toolbar`       | `"horizontal"`, `"vertical"` | `"horizontal"` |
| `loopFocus`             | `Toolbar`       | boolean                      | `true`         |
| `disabled`              | `Toolbar`       | boolean                      | `false`        |
| `focusableWhenDisabled` | `ToolbarButton` | boolean                      | —              |

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](https://base-ui.com/react/components/toolbar#api-reference) for keyboard behavior and root props.
