# Toolbar

Groups related controls into a single keyboard navigation region.

```tsx
import { AlignLeft, AlignRight } from "lucide-react"
import {
  Toolbar,
  ToolbarButton,
  ToolbarToggle,
  ToolbarGroup,
  ToolbarLink,
  ToolbarSeparator,
} from "@/components/ui/toolbar"
import { ToggleGroup } from "@/components/ui/toggle-group"

export default function ToolbarDemo() {
  return (
    <Toolbar aria-label="Formatting">
      <ToggleGroup aria-label="Alignment" defaultValue={["left"]} spacing={0}>
        <ToolbarToggle value="left" aria-label="Align left">
          <AlignLeft />
        </ToolbarToggle>
        <ToolbarToggle value="right" aria-label="Align right">
          <AlignRight />
        </ToolbarToggle>
      </ToggleGroup>
      <ToolbarSeparator />
      <ToolbarGroup aria-label="Numerical format">
        <ToolbarButton aria-label="Format as currency">$</ToolbarButton>
        <ToolbarButton aria-label="Format as percent">%</ToolbarButton>
      </ToolbarGroup>
      <ToolbarSeparator />
      <ToolbarLink href="#usage">Edited 51m ago</ToolbarLink>
    </Toolbar>
  )
}
```

## Installation

1. Install the following dependencies:

   ```bash
   npm install @base-ui/react @stylexjs/stylex
   ```

2. Set up [Installation](https://carbon.style/docs/installation), including `states.ts`, `state-styles.ts`, and `interop.css` for icons. Install [Button](https://carbon.style/docs/components/button), [Input](https://carbon.style/docs/components/input), [Separator](https://carbon.style/docs/components/separator), and [Toggle](https://carbon.style/docs/components/toggle) for shared styles and parts.

3. Copy and paste the following code into your project.

   ```tsx title="components/ui/toolbar.tsx"
   "use client"

   import type { CSSProperties } from "react"
   import { Toolbar as ToolbarPrimitive } from "@base-ui/react/toolbar"
   import * as stylex from "@stylexjs/stylex"
   import type { StyleXStyles } from "@stylexjs/stylex"

   import { buttonStyles, type ButtonSize, type ButtonVariant } from "@/components/ui/button"
   import { inputStyles } from "@/components/ui/input"
   import { Toggle, ToggleGroupContext, type ToggleProps } from "@/components/ui/toggle"
   import { separatorStyles } from "@/components/ui/separator"
   import { stateStyles } from "@/lib/state-styles"
   import { states } from "@/theme/states"
   import { color, radius, space } from "@/theme/tokens.stylex"

   const styles = stylex.create({
     root: {
       display: "flex",
       alignItems: "center",
       gap: space.s1,
       width: "fit-content",
       padding: space.s1,
       borderRadius: radius.xl,
     },
     vertical: { flexDirection: "column", alignItems: "stretch" },
     group: { display: "flex", alignItems: "center", gap: space.s1 },
     separator: { marginInline: space.s1, marginBlock: space.s1 },
     ghostOnWell: {
       backgroundColor: { default: null, ":hover": color.itemHighlightBg },
       "--carbon-menu-open-bg": color.itemHighlightBg,
     },
   })
   const toolbarStyles = {
     ...styles,
     root: [states.well, styles.root],
     button: buttonStyles.base,
     variants: buttonStyles.variants,
     sizes: buttonStyles.sizes,
     input: inputStyles.base,
   }
   type Overrides = { xstyle?: StyleXStyles; className?: string; style?: CSSProperties }
   type ButtonOptions = { variant?: ButtonVariant; size?: ButtonSize }

   function Toolbar({
     xstyle,
     className,
     style,
     children,
     ...props
   }: Omit<ToolbarPrimitive.Root.Props, "className" | "style"> & Overrides) {
     return (
       <ToolbarPrimitive.Root
         data-slot="toolbar"
         {...props}
         {...stateStyles(
           (state) => [
             states.well,
             styles.root,
             state.orientation === "vertical" && styles.vertical,
             xstyle,
           ],
           { className, style },
         )}
       >
         <ToggleGroupContext value={{ onWell: true }}>{children}</ToggleGroupContext>
       </ToolbarPrimitive.Root>
     )
   }
   function ToolbarGroup({
     xstyle,
     className,
     style,
     ...props
   }: Omit<ToolbarPrimitive.Group.Props, "className" | "style"> & Overrides) {
     return (
       <ToolbarPrimitive.Group
         data-slot="toolbar-group"
         {...props}
         {...stateStyles(
           (state) => [styles.group, state.orientation === "vertical" && styles.vertical, xstyle],
           { className, style },
         )}
       />
     )
   }
   function ToolbarButton({
     variant = "ghost",
     size = "default",
     xstyle,
     className,
     style,
     ...props
   }: Omit<ToolbarPrimitive.Button.Props, "className" | "style"> & Overrides & ButtonOptions) {
     return (
       <ToolbarPrimitive.Button
         data-slot="toolbar-button"
         data-variant={variant}
         data-size={size}
         {...props}
         {...stateStyles(
           (state) => [
             buttonStyles.base,
             buttonStyles.variants[variant],
             variant === "ghost" && styles.ghostOnWell,
             buttonStyles.sizes[size],
             state.disabled && states.disabled,
             xstyle,
           ],
           { className, style, hook: "carbon-icons" },
         )}
       />
     )
   }
   function ToolbarToggle<Value extends string = string>({
     focusableWhenDisabled = true,
     ...props
   }: ToggleProps<Value> & { focusableWhenDisabled?: boolean }) {
     return (
       <ToolbarPrimitive.Button
         disabled={props.disabled}
         nativeButton={props.nativeButton}
         focusableWhenDisabled={focusableWhenDisabled}
         render={<Toggle data-slot="toolbar-toggle" {...props} />}
       />
     )
   }

   function ToolbarLink({
     variant = "link",
     size = "default",
     xstyle,
     className,
     style,
     ...props
   }: Omit<ToolbarPrimitive.Link.Props, "className" | "style"> & Overrides & ButtonOptions) {
     return (
       <ToolbarPrimitive.Link
         data-slot="toolbar-link"
         data-variant={variant}
         data-size={size}
         {...props}
         {...stateStyles(
           () => [buttonStyles.base, buttonStyles.variants[variant], buttonStyles.sizes[size], xstyle],
           { className, style, hook: "carbon-icons" },
         )}
       />
     )
   }
   function ToolbarInput({
     xstyle,
     className,
     style,
     ...props
   }: Omit<ToolbarPrimitive.Input.Props, "className" | "style"> & Overrides) {
     return (
       <ToolbarPrimitive.Input
         data-slot="toolbar-input"
         {...props}
         {...stateStyles((state) => [inputStyles.base, state.disabled && states.disabled, xstyle], {
           className,
           style,
         })}
       />
     )
   }
   function ToolbarSeparator({
     xstyle,
     className,
     style,
     ...props
   }: Omit<ToolbarPrimitive.Separator.Props, "className" | "style"> & Overrides) {
     return (
       <ToolbarPrimitive.Separator
         data-slot="toolbar-separator"
         {...props}
         {...stateStyles(
           (state) => [
             separatorStyles.base,
             separatorStyles.orientations[state.orientation],
             styles.separator,
             xstyle,
           ],
           { className, style },
         )}
       />
     )
   }
   export {
     Toolbar,
     ToolbarGroup,
     ToolbarButton,
     ToolbarToggle,
     ToolbarLink,
     ToolbarInput,
     ToolbarSeparator,
     toolbarStyles,
   }
   ```

4. Update the import paths to match your project setup.

## Usage

```tsx
import { Toolbar, ToolbarButton, ToolbarSeparator } from "@/components/ui/toolbar"
```

```tsx
<Toolbar aria-label="Document actions">
  <ToolbarButton>Undo</ToolbarButton>
  <ToolbarSeparator />
  <ToolbarButton>Redo</ToolbarButton>
</Toolbar>
```

Give the toolbar an `aria-label`. In a horizontal toolbar, place at most one `ToolbarInput` last so arrow navigation does not conflict with moving the caret.

Use `ToolbarToggle` inside [Toggle Group](https://carbon.style/docs/components/toggle-group) for mutually exclusive or multiple selections.

## Using with Menu

Pass `DropdownMenuTrigger` to the button's `render` prop.

```tsx
import { Toolbar, ToolbarButton } from "@/components/ui/toolbar"
import {
  DropdownMenu,
  DropdownMenuTrigger,
  DropdownMenuContent,
  DropdownMenuItem,
} from "@/components/ui/dropdown-menu"
export default function ToolbarMenu() {
  return (
    <Toolbar aria-label="Document">
      <ToolbarButton>Save</ToolbarButton>
      <DropdownMenu>
        <ToolbarButton render={<DropdownMenuTrigger />}>More</ToolbarButton>
        <DropdownMenuContent>
          <DropdownMenuItem>Print</DropdownMenuItem>
          <DropdownMenuItem>Export</DropdownMenuItem>
        </DropdownMenuContent>
      </DropdownMenu>
    </Toolbar>
  )
}
```

## Using with Tooltip

Pass `ToolbarButton` to `TooltipTrigger`'s `render` prop.

```tsx
import { Bold } from "lucide-react"
import { Toolbar, ToolbarButton } from "@/components/ui/toolbar"
import { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from "@/components/ui/tooltip"
export default function ToolbarTooltip() {
  return (
    <TooltipProvider>
      <Toolbar aria-label="Text">
        <Tooltip>
          <TooltipTrigger render={<ToolbarButton aria-label="Bold" size="icon" />}>
            <Bold />
          </TooltipTrigger>
          <TooltipContent>Bold</TooltipContent>
        </Tooltip>
      </Toolbar>
    </TooltipProvider>
  )
}
```

## Using with NumberField

Use `NumberFieldToolbarInput` from [Number Field](https://carbon.style/docs/components/number-field) as the last control.

```tsx
import * as stylex from "@stylexjs/stylex"
import { Toolbar, ToolbarButton, ToolbarSeparator } from "@/components/ui/toolbar"
import { NumberField, NumberFieldGroup, NumberFieldToolbarInput } from "@/components/ui/number-field"
const styles = stylex.create({ number: { width: "5rem" } })
export default function ToolbarNumberField() {
  return (
    <Toolbar aria-label="Text size">
      <ToolbarButton>Reset</ToolbarButton>
      <ToolbarSeparator />
      <NumberField defaultValue={16} min={8} max={96} xstyle={styles.number}>
        <NumberFieldGroup>
          <NumberFieldToolbarInput aria-label="Font size" />
        </NumberFieldGroup>
      </NumberField>
    </Toolbar>
  )
}
```

## Orientation

Use `orientation` to switch between horizontal and vertical navigation.

```tsx
import * as stylex from "@stylexjs/stylex"
import { Toolbar, ToolbarButton, ToolbarSeparator, ToolbarInput } from "@/components/ui/toolbar"
import { VStack } from "@/components/ui/stack"
const styles = stylex.create({ input: { width: "10rem" } })

export default function ToolbarOrientation() {
  return (
    <VStack gap={4}>
      {(["horizontal", "vertical"] as const).map((orientation) => (
        <Toolbar key={orientation} orientation={orientation} aria-label={`${orientation} toolbar`}>
          <ToolbarButton>Undo</ToolbarButton>
          <ToolbarSeparator />
          <ToolbarButton>Redo</ToolbarButton>
          <ToolbarInput
            aria-label="Document name"
            placeholder="Document name"
            xstyle={styles.input}
          />
        </Toolbar>
      ))}
    </VStack>
  )
}
```

## Disabled

Use `disabled` on the toolbar, a group, or an individual control; set `focusableWhenDisabled={false}` to skip a disabled control during navigation.

```tsx
import { Toolbar, ToolbarButton, ToolbarGroup, ToolbarInput } from "@/components/ui/toolbar"
import { VStack } from "@/components/ui/stack"
export default function ToolbarDisabled() {
  return (
    <VStack gap={4}>
      <Toolbar aria-label="Partially disabled">
        <ToolbarButton>Undo</ToolbarButton>
        <ToolbarButton disabled>Redo</ToolbarButton>
        <ToolbarButton disabled focusableWhenDisabled={false}>
          Delete
        </ToolbarButton>
        <ToolbarGroup disabled>
          <ToolbarButton>Copy</ToolbarButton>
        </ToolbarGroup>
      </Toolbar>
      <Toolbar disabled aria-label="Disabled toolbar">
        <ToolbarButton>Save</ToolbarButton>
        <ToolbarInput aria-label="Name" defaultValue="Untitled" />
      </Toolbar>
    </VStack>
  )
}
```

## API reference

Parts accept [Base UI Toolbar props](https://base-ui.com/react/components/toolbar), including `render`. Styled parts accept `xstyle`, `className`, and inline `style`. `ToolbarToggle` accepts [Toggle props](https://carbon.style/docs/components/toggle) and `focusableWhenDisabled` (default `true`).

### ToolbarButton and ToolbarLink

| Prop      | Type                                                                                 | Default                             |
| --------- | ------------------------------------------------------------------------------------ | ----------------------------------- |
| `variant` | `"default" \| "outline" \| "secondary" \| "ghost" \| "destructive" \| "link"`        | `"ghost"` (button), `"link"` (link) |
| `size`    | `"default" \| "xs" \| "sm" \| "lg" \| "icon" \| "icon-xs" \| "icon-sm" \| "icon-lg"` | `"default"`                         |

### toolbarStyles

| Key                 | What it is                                          |
| ------------------- | --------------------------------------------------- |
| `root`              | The well, inset and nested corner                   |
| `vertical`          | Vertical arrangement                                |
| `group`             | Group layout                                        |
| `separator`         | Separator spacing                                   |
| `ghostOnWell`       | Row-highlight hover and open fill for ghost buttons |
| `button`            | Button base and focus ring                          |
| `variants`, `sizes` | Button appearance maps                              |
| `input`             | Input base and focus ring                           |

### Data attributes

| Attribute                       | On                    | Effect                             |
| ------------------------------- | --------------------- | ---------------------------------- |
| `data-slot="toolbar"`           | root                  | Toolbar anatomy                    |
| `data-slot="toolbar-group"`     | group                 | Group anatomy                      |
| `data-slot="toolbar-button"`    | button                | Action anatomy                     |
| `data-slot="toolbar-toggle"`    | toggle                | Selection anatomy                  |
| `data-slot="toolbar-link"`      | link                  | Link anatomy                       |
| `data-slot="toolbar-input"`     | input                 | Input anatomy                      |
| `data-slot="toolbar-separator"` | separator             | Separator anatomy                  |
| `data-orientation`              | parts                 | Horizontal or vertical             |
| `data-disabled`                 | root, group, controls | Disabled state                     |
| `data-focusable`                | button, input         | Disabled control remains focusable |
| `data-pressed`                  | toggle                | Selected state                     |
| `data-variant`, `data-size`     | button, link, toggle  | Appearance                         |
