Skip to the content

ComponentsPanel

Toolbar

A bar of buttons, toggles and links that the keyboard reaches as one stop and moves through with the arrows.

Import

import { Toolbar, ToolbarGroup, ToolbarButton, ToolbarOverflowButton, ToolbarLink, ToolbarToggleGroup, ToolbarToggleItem, ToolbarSeparator } from "@booleanpress/ui/toolbar"

Usage

A toolbar groups the controls that act on one thing: an editor's formatting, a list's bulk actions. Tab reaches it once; the arrow keys move between its buttons. Use it for three or more controls; two buttons side by side are a button group.

import { BoldIcon, ItalicIcon } from "lucide-react"
import { Toolbar, ToolbarButton } from "@booleanpress/ui/toolbar"

export function Formatting() {
  return (
    <Toolbar aria-label="Formatting">
      <ToolbarButton aria-label="Bold"><BoldIcon /></ToolbarButton>
      <ToolbarButton aria-label="Italic"><ItalicIcon /></ToolbarButton>
    </Toolbar>
  )
}

Name the toolbar with aria-label. ToolbarGroup gathers controls: the bar spreads its groups out, so two groups sit at the start and the end, and three at the start, centre and end. ToolbarButton takes Button's variant, size, severity and loading, and defaults to the muted ghost look. ToolbarToggleGroup and ToolbarToggleItem are a toggle group inside the toolbar (type="single" or "multiple"), ToolbarLink a link, and ToolbarSeparator a line between groups. ToolbarOverflowButton is an ellipsis button named by the provider's moreActions string: put it in a dropdown menu's trigger for the actions that do not fit. orientation="vertical" stacks the controls and moves with โ†‘ and โ†“. A field inside the bar, such as a search box, is its own tab stop.

Examples

Basic

An editor's formatting buttons in three groups, with a disabled Redo the arrows skip.

import { BoldIcon, ItalicIcon, LinkIcon, ListIcon, ListOrderedIcon, Redo2Icon, Undo2Icon } from "lucide-react"
import { Toolbar, ToolbarButton, ToolbarGroup, ToolbarSeparator } from "@booleanpress/ui/toolbar"

export default function ToolbarBasic() {
  return (
    <Toolbar aria-label="Email template formatting" className="w-full max-w-xl justify-start">
      <ToolbarGroup>
        <ToolbarButton aria-label="Undo">
          <Undo2Icon />
        </ToolbarButton>
        <ToolbarButton aria-label="Redo" disabled>
          <Redo2Icon />
        </ToolbarButton>
      </ToolbarGroup>
      <ToolbarSeparator />
      <ToolbarGroup>
        <ToolbarButton aria-label="Bold">
          <BoldIcon />
        </ToolbarButton>
        <ToolbarButton aria-label="Italic">
          <ItalicIcon />
        </ToolbarButton>
        <ToolbarButton aria-label="Insert link">
          <LinkIcon />
        </ToolbarButton>
      </ToolbarGroup>
      <ToolbarSeparator />
      <ToolbarGroup>
        <ToolbarButton aria-label="Bulleted list">
          <ListIcon />
        </ToolbarButton>
        <ToolbarButton aria-label="Numbered list">
          <ListOrderedIcon />
        </ToolbarButton>
      </ToolbarGroup>
    </Toolbar>
  )
}

Start, centre and end

Three groups spread across the bar: icon buttons, a search field and the form's buttons.

import { PlusIcon, PrinterIcon, UploadIcon } from "lucide-react"
import { Input } from "@booleanpress/ui/input"
import { Toolbar, ToolbarButton, ToolbarGroup } from "@booleanpress/ui/toolbar"

export default function ToolbarStartCenterEnd() {
  return (
    <Toolbar aria-label="Contacts" className="w-full max-w-2xl">
      <ToolbarGroup>
        <ToolbarButton aria-label="New contact">
          <PlusIcon />
        </ToolbarButton>
        <ToolbarButton aria-label="Print">
          <PrinterIcon />
        </ToolbarButton>
        <ToolbarButton aria-label="Import contacts">
          <UploadIcon />
        </ToolbarButton>
      </ToolbarGroup>
      <ToolbarGroup>
        <Input aria-label="Search contacts" placeholder="Search" className="w-48" />
      </ToolbarGroup>
      <ToolbarGroup>
        <ToolbarButton variant="outline" size="sm">
          Cancel
        </ToolbarButton>
        <ToolbarButton variant="default" size="sm">
          Save
        </ToolbarButton>
      </ToolbarGroup>
    </Toolbar>
  )
}

With toggle groups

A multiple group for the text style and a single group for the alignment.

import { AlignCenterIcon, AlignLeftIcon, AlignRightIcon, BoldIcon, ItalicIcon, UnderlineIcon } from "lucide-react"
import {
  Toolbar,
  ToolbarButton,
  ToolbarSeparator,
  ToolbarToggleGroup,
  ToolbarToggleItem,
} from "@booleanpress/ui/toolbar"

export default function ToolbarToggleGroups() {
  return (
    <Toolbar aria-label="Signature formatting" className="w-full max-w-xl justify-start">
      <ToolbarToggleGroup type="multiple" aria-label="Text style" defaultValue={["bold"]}>
        <ToolbarToggleItem value="bold" aria-label="Bold">
          <BoldIcon />
        </ToolbarToggleItem>
        <ToolbarToggleItem value="italic" aria-label="Italic">
          <ItalicIcon />
        </ToolbarToggleItem>
        <ToolbarToggleItem value="underline" aria-label="Underline">
          <UnderlineIcon />
        </ToolbarToggleItem>
      </ToolbarToggleGroup>
      <ToolbarSeparator />
      <ToolbarToggleGroup type="single" aria-label="Alignment" defaultValue="left">
        <ToolbarToggleItem value="left" aria-label="Align left">
          <AlignLeftIcon />
        </ToolbarToggleItem>
        <ToolbarToggleItem value="center" aria-label="Align centre">
          <AlignCenterIcon />
        </ToolbarToggleItem>
        <ToolbarToggleItem value="right" aria-label="Align right">
          <AlignRightIcon />
        </ToolbarToggleItem>
      </ToolbarToggleGroup>
      <ToolbarButton variant="outline" size="sm" className="ms-auto">
        Preview
      </ToolbarButton>
    </Toolbar>
  )
}

Vertical

orientation="vertical" stacks the buttons; โ†‘ and โ†“ move between them.

import { DownloadIcon, FilterIcon, RefreshCwIcon, SettingsIcon } from "lucide-react"
import { Toolbar, ToolbarButton, ToolbarGroup, ToolbarSeparator } from "@booleanpress/ui/toolbar"

export default function ToolbarVertical() {
  return (
    <Toolbar orientation="vertical" aria-label="Delivery log">
      <ToolbarGroup>
        <ToolbarButton aria-label="Refresh">
          <RefreshCwIcon />
        </ToolbarButton>
        <ToolbarButton aria-label="Filter">
          <FilterIcon />
        </ToolbarButton>
        <ToolbarButton aria-label="Export as CSV">
          <DownloadIcon />
        </ToolbarButton>
      </ToolbarGroup>
      <ToolbarSeparator />
      <ToolbarButton aria-label="Log settings">
        <SettingsIcon />
      </ToolbarButton>
    </Toolbar>
  )
}

Overflow menu

A ToolbarOverflowButton at the end opens a dropdown menu of further actions.

import { ArchiveIcon, CopyIcon, MailIcon, ReplyIcon, Trash2Icon } from "lucide-react"
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuSeparator,
  DropdownMenuTrigger,
} from "@booleanpress/ui/dropdown-menu"
import { Toolbar, ToolbarButton, ToolbarGroup, ToolbarOverflowButton } from "@booleanpress/ui/toolbar"

export default function ToolbarOverflowMenu() {
  return (
    <Toolbar aria-label="Ticket #4821" className="w-full max-w-md">
      <ToolbarGroup>
        <ToolbarButton>
          <ReplyIcon /> Reply
        </ToolbarButton>
        <ToolbarButton>
          <ArchiveIcon /> Close ticket
        </ToolbarButton>
      </ToolbarGroup>
      <DropdownMenu>
        <DropdownMenuTrigger asChild>
          {/* Named by the provider's "More actions" string. */}
          <ToolbarOverflowButton />
        </DropdownMenuTrigger>
        <DropdownMenuContent align="end">
          <DropdownMenuItem>
            <MailIcon /> Mark as unread
          </DropdownMenuItem>
          <DropdownMenuItem>
            <CopyIcon /> Copy link
          </DropdownMenuItem>
          <DropdownMenuSeparator />
          <DropdownMenuItem variant="destructive">
            <Trash2Icon /> Delete ticket
          </DropdownMenuItem>
        </DropdownMenuContent>
      </DropdownMenu>
    </Toolbar>
  )
}

Accessibility

Semantics
A div with role="toolbar" and aria-orientation. Buttons and links are native; toggle items are buttons with aria-pressed (role="radio" and aria-checked in a single group); a group is role="group"; a separator is role="separator".
Labels
Name the toolbar (aria-label or aria-labelledby) and every icon-only control. The overflow button is named by the provider's moreActions string unless you pass aria-label.
Focus
The toolbar is one tab stop: Tab lands on the last control used (the first at the start), the arrows move from control to control, wrapping at the ends, and Tab leaves. Disabled controls are skipped. Focus is a 1px --ring outline 2px outside the control.
Known limits
  • A text field inside the toolbar keeps its own arrow keys and is a separate tab stop.
  • Groups wrap onto a new line when the bar is narrow; the arrow keys follow the source order.

Keyboard

Keyboard
KeyBehaviour
TabMoves into the toolbar, onto the last control used, and out again: one stop.
โ†’orโ†Moves to the next or previous control, wrapping at the ends (โ†“ and โ†‘ when vertical). Reversed in a right-to-left page.
HomeorEndMoves to the first or last control.
EnterorSpaceActivates the focused button, or toggles the focused toggle item.

API

Toolbar

Renders Radix Toolbar.Root and passes it every other prop.

Toolbar props
PropTypeDefaultDescription
asChildboolean
dir"ltr" | "rtl"The reading direction. The provider's dir by default.
loopbooleanWhether the arrows wrap from the last control to the first. true by default.
orientation"horizontal" | "vertical"horizontalhorizontal (default) or vertical: the layout, and which arrow keys move.

ToolbarGroup

Renders a div and passes it every other prop.

ToolbarButton

ToolbarButton props
PropTypeDefaultDescription
data-slotstringtoolbar-button
loadingbooleanShows the spinner and sets aria-busy and aria-disabled, as Button's: the button ignores presses but keeps its focus, and the arrow keys still reach it.
raisedboolean | nullLifts the button on a shadow.
roundedboolean | nullA pill, or a circle for an icon button.
severity"success" | "info" | "warning" | "help" | "danger" | "contrast" | nullButton's severity colour: success, info, warning, help, danger, contrast.
size"default" | "xs" | "sm" | "lg" | "icon" | "icon-xs" | "icon-sm" | "icon-lg" | nullButton's size: default, xs, sm, lg, or a square icon size.
variant"link" | "default" | "destructive" | "outline" | "secondary" | "ghost" | nullghostThe button's look, as Button's. ghost by default: muted text that takes the faintest surface on hover.

ToolbarOverflowButton

ToolbarOverflowButton props
PropTypeDefaultDescription
loadingbooleanShows the spinner and sets aria-busy and aria-disabled, as Button's: the button ignores presses but keeps its focus, and the arrow keys still reach it.
raisedboolean | null
roundedboolean | null
severity"success" | "info" | "warning" | "help" | "danger" | "contrast" | null
size"default" | "xs" | "sm" | "lg" | "icon" | "icon-xs" | "icon-sm" | "icon-lg" | null
variant"link" | "default" | "destructive" | "outline" | "secondary" | "ghost" | nullThe button's look, as Button's. ghost by default: muted text that takes the faintest surface on hover.

ToolbarLink

Renders Radix Toolbar.Link and passes it every other prop.

ToolbarLink props
PropTypeDefaultDescription
asChildbooleanRender the child element (a router's link) instead, with the link's behaviour and classes merged onto it.

ToolbarToggleGroup

Renders Radix Toolbar.ToggleGroup and passes it every other prop.

ToolbarToggleGroup props
PropTypeDefaultDescription
typerequired"single" | "multiple"single (one item on at a time) or multiple. Required.
asChildboolean
defaultValuestring | string[]The value of the item that is pressed when initially rendered. Use defaultValue if you do not need to control the state of a toggle group. The value of the items that are pressed when initially rendered. Use defaultValue if you do not need to control the state of a toggle group.
dir"ltr" | "rtl"
disabledbooleanWhether the group is disabled from user interaction.
loopboolean
onValueChange((value: string) => void) | ((value: string[]) => void)The callback that fires when the value of the toggle group changes. The callback that fires when the state of the toggle group changes.
orientation"horizontal" | "vertical"
rovingFocusbooleanWhether the group should maintain roving focus of its buttons.
size"default" | "sm" | "lg" | nullsm, default or lg, for every item. Defaults to the provider's controlSize.
valuestring | string[]The controlled stateful value of the item that is pressed. The controlled stateful value of the items that are pressed.

ToolbarToggleItem

Renders Radix Toolbar.ToggleItem and passes it every other prop.

ToolbarToggleItem props
PropTypeDefaultDescription
asChildboolean
valuestring | string[]The controlled stateful value of the item that is pressed. The controlled stateful value of the items that are pressed.

ToolbarSeparator

Renders Radix Toolbar.Separator and passes it every other prop.

ToolbarSeparator props
PropTypeDefaultDescription
asChildboolean
decorativebooleanWhether or not the component is purely decorative. When true, accessibility-related attributes are updated so that that the rendered element is removed from the accessibility tree.
orientation"horizontal" | "vertical"Either vertical or horizontal. Defaults to horizontal.

Every part takes className, merged with its defaults by cn(), and ref, which reaches the element it renders.

Data attributes: data-slot="toolbar" (Toolbar), data-slot="toolbar-group" (ToolbarGroup), data-slot="toolbar-overflow-button" (ToolbarOverflowButton), data-slot="toolbar-link" (ToolbarLink), data-slot="toolbar-toggle-group" (ToolbarToggleGroup), data-slot="toolbar-toggle-item" (ToolbarToggleItem), data-slot="toolbar-separator" (ToolbarSeparator), and data-size.

Provider strings: moreActions (BooleanUIProvider's strings).

Theming

The bar is --card with a 1px --border edge, a 6px radius and 10px padding. Buttons are --muted-foreground on transparent, with --subtle under the pointer; toggle items take the toggle's look; separators are --border; focus is --ring.

The tokens its classes read. Change their values in your theme, and it follows.

Theme tokens
TokenUsed for
--accent-foregroundtext
--borderbackground
--cardbackground
--card-foregroundtext
--muted-foregroundtext