Toolbar
A raised strip of controls — buttons, toggle groups, links, entry wells — with a single tab stop; arrow keys rove between items.
Tab once into the strip, then arrow between the toggles and buttons.
tsx
"use client"
import { Bold, Italic, Redo2, Underline, Undo2 } from "lucide-react"
import { Toggle } from "@/registry/seam/ui/toggle"
import { ToggleGroup } from "@/registry/seam/ui/toggle-group"
import {
Toolbar,
ToolbarButton,
ToolbarGroup,
ToolbarSeparator,
} from "@/registry/seam/ui/toolbar"
// One tab stop for the whole strip — arrow keys move between controls,
// including the embedded toggle group (its own well inside the raised strip).
export default function ToolbarDemo() {
return (
<Toolbar aria-label="Formatting">
<ToggleGroup multiple aria-label="Text style">
<Toggle value="bold" aria-label="Bold">
<Bold />
</Toggle>
<Toggle value="italic" aria-label="Italic">
<Italic />
</Toggle>
<Toggle value="underline" aria-label="Underline">
<Underline />
</Toggle>
</ToggleGroup>
<ToolbarSeparator />
<ToolbarGroup aria-label="History">
<ToolbarButton size="icon" className="size-9" aria-label="Undo">
<Undo2 />
</ToolbarButton>
<ToolbarButton
size="icon"
className="size-9"
aria-label="Redo"
disabled
>
<Redo2 />
</ToolbarButton>
</ToolbarGroup>
</Toolbar>
)
}bunx --bun @seamui/cli@latest add toolbarAPI
| Prop | Type | Default | Description |
|---|---|---|---|
| orientation | "horizontal" | "vertical" | "horizontal" | Layout and arrow-key axis; the separator flips automatically. |
| loopFocus | boolean | true | Arrow navigation wraps at the ends. |
| variant / size | buttonVariants props | "ghost" / "sm" | On ToolbarButton — it wears the seam Button's cva. |
Parts: Toolbar, ToolbarButton, ToolbarLink, ToolbarGroup, ToolbarSeparator, ToolbarInput. Embed a seam ToggleGroup directly — it joins the roving focus.
Notes
- Base UI manages the roving focus through each item's ref, so ToolbarButton attaches press motion via the render prop (§5A Pattern A) instead of wrapping in the Button component — the wrapper would swallow the ref and kill arrow-key navigation.
- The strip is a raised surface (
shadow-resting); ToolbarInput is an entry well carved into it, and an embedded ToggleGroup keeps its own debossed well — keys on a key, wells in a key, per §1. - ToolbarButton presses with seam depth and taps the haptic on pointerdown; ToolbarLink stays a real link with no press depth, like pagination.
Press feedback, reduced motion, and haptics follow the global policy — see Motion and Haptics.