- Accordion
- Avatar
- Badge
- Breadcrumb
- Button
- Calendar
- ChatContainer
- ChatInput
- ChatMessage
- ChatMultiChoiceQuestion
- ChatMultiOptionQuestion
- ChatThinking
- Checkbox
- Combobox
- Container
- CurrencyInput
- DistributionSlider
- Drawer
- Dropdown
- FilePicker
- Grid
- Heading
- Image
- Input
- InputGroup
- Label
- Logo
- MapPin
- Markdown
- Modal
- NativeSelect
- NumberInput
- OptionSlider
- OtpInput
- PhoneInput
- Popover
- Progress
- PropertyCalendar
- RadioGroup
- RadioGroupCards
- ResponsiveModal
- ScrollArea
- SearchBar
- SearchBarFallback
- SearchInput
- Select
- Separator
- Spinner
- Switch
- Table
- Tabs
- Text
- Textarea
- TimePicker
- Toast
- Toggle
- ToggleCard
- ToggleGroup
- Toolbar
- Tooltip
Toolbar
Surface for grouping icon-button actions with arrow-key roving focus
pnpm add @wandercom/design-system-web
import { IconArrowDown } from '@central-icons-react/round-outlined-radius-2-stroke-1.5/IconArrowDown';
import { IconArrowUp } from '@central-icons-react/round-outlined-radius-2-stroke-1.5/IconArrowUp';
import { IconTrashCan } from '@central-icons-react/round-outlined-radius-2-stroke-1.5/IconTrashCan';
import {
Toolbar,
ToolbarButton,
ToolbarGroup,
ToolbarSeparator,
} from '@wandercom/design-system-web/ui/toolbar';
export function Example() {
return (
<Toolbar aria-label="Property card actions">
<ToolbarGroup>
<ToolbarButton aria-label="Move up">
<IconArrowUp />
</ToolbarButton>
<ToolbarButton aria-label="Move down">
<IconArrowDown />
</ToolbarButton>
</ToolbarGroup>
<ToolbarSeparator />
<ToolbarButton aria-label="Delete">
<IconTrashCan />
</ToolbarButton>
</Toolbar>
);
}The default example shows the typical layout — two grouped actions, a separator, two more grouped actions, another separator, and a delete action. The floating variant (default) renders the toolbar as a blurred, elevated surface intended to hover over canvas-style content like cards or media.
Toolbar is built on Base UI's Toolbar primitive. The root provides the surface (defined per variant) and the role="toolbar" landmark with arrow-key roving focus. Inside, place any combination of:
ToolbarGroup— a logical cluster of related buttons (2px gap inside the cluster, 4px gap between siblings of the toolbar root). WrapsToolbar.Groupso a whole cluster can be disabled at once via thedisabledprop.ToolbarSeparator— a 1×16 hairline divider between groups, rendered asToolbar.Separator.ToolbarButton— a 32×32 ghost icon button rendered asToolbar.Button, withhover,focus-visible, and pressed states.
The root accepts arbitrary children, so popup triggers, toggles, or other primitives can be dropped in alongside the standard subcomponents.
Use Base UI's render prop on ToolbarButton to delegate rendering to another primitive while keeping the toolbar's button styles. This is the recommended pattern when wrapping a Menu.Trigger, Popover.Trigger, or Dialog.Trigger.
<Menu.Root>
<ToolbarButton aria-label="More actions" render={<Menu.Trigger />}>
<IconDotsHorizontal />
</ToolbarButton>
<Menu.Portal>{/* … */}</Menu.Portal>
</Menu.Root>Tooltips work the other way around — Base UI's recommended pattern is to pass a ToolbarButton to Tooltip.Trigger's render prop:
<Tooltip.Root>
<Tooltip.Trigger render={<ToolbarButton aria-label="Delete" />}>
<IconTrashCan />
</Tooltip.Trigger>
<Tooltip.Portal>{/* … */}</Tooltip.Portal>
</Tooltip.Root>For toggle-style buttons, drive the visual state via aria-pressed (toggle semantics) or data-state="on" (when composed under a primitive that already manages state).
<ToolbarButton aria-label="Edit" aria-pressed={isEditing}>
<IconPencil />
</ToolbarButton>Plus all props from Base UI Toolbar.Root.
variant?:
aria-label?:
orientation?:
loopFocus?:
disabled?:
render?:
className?:
Plus all props from Base UI Toolbar.Group.
disabled?:
render?:
className?:
Plus all props from Base UI Toolbar.Separator.
orientation?:
render?:
className?:
Plus all props from Base UI Toolbar.Button.
aria-label:
render?:
aria-pressed?:
disabled?:
focusableWhenDisabled?:
className?:
- The toolbar root renders as
role="toolbar"witharia-orientationand anaria-label, so it is exposed as a landmark to assistive tech. - Roving focus is handled by Base UI: only one toolbar item is in the page tab sequence at a time, and arrow keys move between items (
ArrowLeft/ArrowRightfor horizontal,ArrowUp/ArrowDownfor vertical).HomeandEndjump to the first and last items. PressTabto leave the toolbar. ToolbarGroupis a visual cluster — it controls the in-cluster gap (2px) versus the inter-cluster gap (4px) on the toolbar root, and forwards adisabledprop to every contained item.ToolbarSeparatoris rendered asToolbar.Separatorwithrole="separator"and an inverse orientation to the toolbar.ToolbarButtonis icon-only. Always pass anaria-labelso screen readers can announce the action.- Pressed state can be expressed via either
aria-pressed(toggle semantics) ordata-state="on"(when composed under a primitive that drives state, like aMenu.Trigger). Both styles light up the same pressed background. focus-visibleshows a 2px ring rendered with a 1px gap from the button edge, so the indicator stays legible against both the toolbar surface and the underlying canvas.
- The toolbar is
w-fit— it never stretches to fill its container. Wrap it in a positioning element when overlaying canvas content. - Disabled items are skipped by the roving focus by default. Pass
focusableWhenDisabledonToolbarButtonto keep them reachable so screen readers can announce the disabled state. - When using
renderto swap in another primitive's trigger, that primitive owns the underlying element type (button, anchor, etc.) — Base UI merges the toolbar button's props and refs onto it.