Experience System

Toolbar

Grouped controls for rich editors and tool palettes with roving focus, toggle groups, links, and separators.

Drag handle to resize toolbar width

Installation

The components are exported from @by/experience-system. Add the package with your package manager:

pnpm add @by/experience-system

In this monorepo, depend on the workspace package (for example via workspace:* or your catalog) so imports resolve to packages/experience-system.

Composition

Build a Toolbar from ToolbarToggleGroup / ToolbarToggleItem, ToolbarGroup / ToolbarGroupButton, ToolbarButton, ToolbarLink, ToolbarSeparator, ToolbarZoneSplitter, and optionally ToolbarOverflowItem for non-primitive controls:

Toolbar
├── ToolbarToggleGroup
│   └── ToolbarToggleItem …
├── ToolbarGroup
│   └── ToolbarGroupButton …
├── ToolbarSeparator (optional)
├── ToolbarZoneSplitter (optional, flex-grow zone spacer)
├── ToolbarButton | ToolbarLink …
├── ToolbarOverflowItem (optional, wraps non-primitive controls)
└── …

Other primitives with asChild triggers (for example DropdownMenu) can be composed inside the toolbar per Radix composition. See Radix Toolbar.

Usage

import {
  Toolbar,
  ToolbarButton,
  ToolbarGroup,
  ToolbarGroupButton,
  ToolbarOverflowItem,
  ToolbarSeparator,
  ToolbarToggleGroup,
  ToolbarToggleItem,
  ToolbarZoneSplitter,
} from '@by/experience-system';

All toolbar parts are client components ('use client'). Use them inside a Client Component or a dynamic import when using the Next.js App Router.

<Toolbar aria-label="Actions" orientation="horizontal" loop>
  <ToolbarToggleGroup type="single" defaultValue="left" aria-label="Align">
    <ToolbarToggleItem value="left" aria-label="Left">
      L
    </ToolbarToggleItem>
    <ToolbarToggleItem value="right" aria-label="Right">
      R
    </ToolbarToggleItem>
  </ToolbarToggleGroup>
  <ToolbarSeparator />
  <ToolbarButton type="button">Save</ToolbarButton>
</Toolbar>

Examples

Overview

Multiple and single ToolbarToggleGroup regions, a ToolbarLink, and a ToolbarButton (same preview as at the top of the page).

Drag handle to resize toolbar width

Toggle groups

Two groups only—style (multiple) and alignment (single).

Groups

ToolbarGroup and ToolbarGroupButton group related actions — Undo/Redo, Cut/Copy/Paste, Zoom In/Out, and Upload/Download — so each set overflows as a single unit. ToolbarToggleItem blocks (Bold/Italic/Underline) still carry selection state alongside the non-toggle groups. Use variant="outline" for a bordered segmented container or variant="ghost" (default) for a flat arrangement. Each ToolbarGroupButton inside the group is a plain action button (no toggle state). Omit icon (default false) for icon + text labels; set icon for icon-only slots.

Drag handle to resize toolbar width

ToolbarButton with ToolbarLink items for secondary navigation.

In card

Place the toolbar in CardAction for header-level actions, or in CardHeader when it controls body content. Narrow the card to see overflow in both placements.

Toolbar in card header

Place the toolbar in CardAction so it aligns to the trailing edge of the header grid.

Toolbar in card body

Place the toolbar inside CardContent when it controls the body content — for example, a rich-text or chart surface.

Drag handle to resize toolbar width

Vertical toolbar

A vertical Toolbar with icon-only ToolbarButton items. Each button uses menuItem and visibility so items collapse into the ellipsis menu when space is constrained along the toolbar axis.

Drag handle to resize toolbar height

Zone splitter

Two ToolbarZoneSplitter elements divide the toolbar into three zones: history on the left, formatting in the center, and transfer/save actions on the right. Splitters absorb free space and do not participate in overflow collapse.

Drag handle to resize toolbar width

Responsive — mixed content

Toggle groups and labeled action buttons share the same overflow container. The entire toggle-group block collapses as a single unit. A ToolbarZoneSplitter separates formatting controls on the left from search and action buttons on the right. ToolbarOverflowItem wraps the search Input. Use visibility="always-visible" to pin items in JSX order; use visibility="always-hidden" to keep an item in the overflow menu only.

Drag handle to resize toolbar width

Use ToolbarAdaptiveItem for three-tier responsive slots: expanded (wide toolbar), collapsed (compact inline fallback), and optional menuItem (ellipsis menu). Set collapseBelow to the toolbar width (or height when vertical) at which the slot switches from expanded to collapsed. Use visibility="always-visible" when the compact form should stay inline while sibling items collapse first.

This differs from the mixed-content example, where search collapses entirely into a DropdownMenuItem with no inline icon fallback.

Drag handle to resize toolbar width

Floating toolbar

A compact formatting toolbar hosted inside a Card — useful for floating palettes and contextual tool surfaces. Overflow collapses items into the ellipsis menu when the container is constrained.

API Reference

The tables below mirror the Radix UI Toolbar API. Subsection titles match @by/experience-system exports. ToolbarButton and ToolbarLink layer Button styles on Radix Toolbar.Button and Toolbar.Link. ToolbarButton, ToolbarSeparator, ToolbarToggleGroup, ToolbarToggleItem, ToolbarOverflowItem, and ToolbarAdaptiveItem all extend ToolbarOverflowableProps (menuItem, visibility) for responsive overflow behaviour.

ToolbarOverflowableProps

Shared interface extended by every toolbar primitive that participates in overflow.

PropTypeDefault
menuItemReact.ReactNode— — content rendered inside the overflow dropdown when this item is hidden
visibility'always-visible' | 'always-hidden' | undefinedundefined
  • 'always-visible' — keeps the item at its declared position among siblings; it is never collapsed into the overflow menu.
  • 'always-hidden' — always places the item in the overflow menu and never renders it in the toolbar.
  • undefined (default) — normal responsive behaviour; item collapses when the toolbar runs out of space.

Toolbar

PropTypeDefault
asChildbooleanfalse
orientationhorizontal | verticalhorizontal
dirltr | rtl
loopbooleantrue
overflowbooleantrue
overflowTriggerLabelstring'More options'
overflowTriggerSizeButton size values'md'
overflowTriggerIconbooleantrue
Data attributeValues
data-slottoolbar
data-orientationhorizontal | vertical

ToolbarButton

PropTypeDefault
asChildbooleanfalse
variantButton variant valuesoutline
sizeButton size valuessm
colorButton color valuesneutral
iconbooleanfalse
menuItemReact.ReactNode
visibility'always-visible' | 'always-hidden' | undefinedundefined
Data attributeValues
data-slottoolbar-button
data-colormirrors color
data-orientationhorizontal | vertical
PropTypeDefault
asChildbooleanfalse
sizeButton size valuessm
Data attributeValues
data-slottoolbar-link
data-orientationhorizontal | vertical

ToolbarSeparator

PropTypeDefault
asChildbooleanfalse
menuItemReact.ReactNode
visibility'always-visible' | 'always-hidden' | undefinedundefined
Data attributeValues
data-slottoolbar-separator
data-orientationhorizontal | vertical

ToolbarToggleGroup

PropTypeDefault
asChildbooleanfalse
typesingle | multiple— (required)
valuestring or string[]
defaultValuestring or string[]
onValueChange(value: string | string[]) => void
disabledbooleanfalse
variantoutline | ghostghost
sizesm | md | lgsm
menuItemReact.ReactNode
visibility'always-visible' | 'always-hidden' | undefinedundefined
Data attributeValues
data-slottoolbar-toggle-group
data-orientationhorizontal | vertical

ToolbarToggleItem

PropTypeDefault
asChildbooleanfalse
valuestring— (required)
disabledboolean
variantoutline | ghostFrom group context
sizesm | md | lgFrom group context
iconbooleanfalse
Data attributeValues
data-slottoolbar-toggle-item
data-icon-onlyPresent when icon is true
data-stateon | off
data-disabledPresent when disabled
data-orientationhorizontal | vertical

ToolbarGroup

A container that groups related action buttons with the same visual chrome as ToolbarToggleGroup but carries no selection state. The entire group collapses into the overflow menu as a single unit. Extends ToolbarOverflowableProps and all standard HTMLDivElement attributes.

PropTypeDefault
variantoutline | ghostghost
sizesm | md | lgmd
orientationhorizontal | verticalInherited from Toolbar context
menuItemReact.ReactNode
visibility'always-visible' | 'always-hidden' | undefinedundefined
Data attributeValues
data-slottoolbar-group
data-orientationhorizontal | vertical

ToolbarGroupButton

A non-toggle button styled as a segment within a ToolbarGroup. Uses the same sizing and chrome classes as ToolbarToggleItem but carries no pressed/checked state. Participates in toolbar keyboard navigation via the Radix Toolbar.Button primitive.

PropTypeDefault
variantoutline | ghostFrom ToolbarGroup context
sizesm | md | lgFrom ToolbarGroup context
iconbooleanfalse
orientationhorizontal | verticalFrom ToolbarGroup context
colorButton color values
disabledboolean
Data attributeValues
data-slottoolbar-group-button
data-colormirrors color
data-icon-onlyPresent when icon is true

ToolbarOverflowItem

A data-carrier wrapper for non-primitive toolbar controls (e.g. Input, custom elements) that cannot accept the menuItem / visibility props directly. Place it as a direct child of <Toolbar> when overflow is enabled.

PropTypeDefault
childrenReact.ReactNode— (required) — rendered in the toolbar when space allows
menuItemReact.ReactNode— — rendered inside the overflow dropdown when this item is hidden
visibility'always-visible' | 'always-hidden' | undefinedundefined

ToolbarOverflowItem does not render any DOM element of its own; it delegates all rendering to the parent Toolbar.

ToolbarAdaptiveItem

A data-carrier for variable-width toolbar controls with three responsive tiers: expanded, collapsed, and optional menuItem in the ellipsis menu. Place it as a direct child of <Toolbar> when overflow is enabled.

PropTypeDefault
expandedReact.ReactNode— (required) — rendered when the toolbar is wide enough
collapsedReact.ReactNode— (required) — compact inline fallback when the toolbar is narrower than collapseBelow
collapseBelownumber480 — toolbar width in px (height when orientation="vertical") below which collapsed is shown
menuItemReact.ReactNode— — rendered inside the overflow dropdown when the entire slot is hidden
visibility'always-visible' | 'always-hidden' | undefinedundefined

ToolbarAdaptiveItem does not render any DOM element of its own when overflow is enabled; the parent Toolbar renders a [data-slot="toolbar-adaptive-item"] wrapper with [data-adaptive-state="expanded" | "collapsed"].

ToolbarZoneSplitter

A layout-only flex-grow spacer for separating toolbar zones. It absorbs remaining space along the toolbar axis so items before it stay left-aligned and items after it group toward the trailing edge. It does not participate in overflow collapse, keyboard focus, or the overflow menu.

PropTypeDefault
classNamestring
Data attributeValues
data-slottoolbar-zone-splitter

Accessibility

Label the Toolbar and each ToolbarToggleGroup with aria-label (or aria-labelledby). Icon-only ToolbarToggleItem values need aria-label. Radix uses roving tabindex across toolbar items. See Radix Toolbar — accessibility.

Keyboard interactions

KeyDescription
TabMoves focus into the toolbar (first item).
SpaceActivates or toggles the focused item.
EnterActivates or toggles the focused item.
ArrowRight / ArrowDownMoves focus to the next item (depends on orientation).
ArrowLeft / ArrowUpMoves focus to the previous item.
HomeMoves focus to the first item.
EndMoves focus to the last item.

Source in the repo: packages/experience-system/src/components/Toolbar/Toolbar.tsx. Agent-oriented contracts: packages/experience-system/src/components/Toolbar/Toolbar.instructions.md.