# Toolbar

A bar of buttons, toggles and menus that the arrow keys move between.

Source: [toolbar.tsx](https://github.com/jakejarvis/jarvis-ui/blob/main/src/components/ui/toolbar.tsx)

```tsx
import {
  AlignCenterIcon,
  AlignLeftIcon,
  AlignRightIcon,
  BoldIcon,
  ItalicIcon,
  LinkIcon,
  UnderlineIcon,
} from "lucide-react";

import {
  Toolbar,
  ToolbarButton,
  ToolbarSeparator,
  ToolbarToggle,
  ToolbarToggleGroup,
} from "@/components/ui/toolbar";

export default function ToolbarBasic() {
  return (
    <Toolbar aria-label="Formatting">
      <ToolbarToggleGroup multiple defaultValue={["bold"]} aria-label="Text style">
        <ToolbarToggle value="bold" aria-label="Bold">
          <BoldIcon />
        </ToolbarToggle>
        <ToolbarToggle value="italic" aria-label="Italic">
          <ItalicIcon />
        </ToolbarToggle>
        <ToolbarToggle value="underline" aria-label="Underline">
          <UnderlineIcon />
        </ToolbarToggle>
      </ToolbarToggleGroup>
      <ToolbarSeparator />
      <ToolbarToggleGroup defaultValue={["left"]} aria-label="Alignment">
        <ToolbarToggle value="left" aria-label="Align left">
          <AlignLeftIcon />
        </ToolbarToggle>
        <ToolbarToggle value="center" aria-label="Align center">
          <AlignCenterIcon />
        </ToolbarToggle>
        <ToolbarToggle value="right" aria-label="Align right">
          <AlignRightIcon />
        </ToolbarToggle>
      </ToolbarToggleGroup>
      <ToolbarSeparator />
      <ToolbarButton size="icon" aria-label="Add link">
        <LinkIcon />
      </ToolbarButton>
    </Toolbar>
  );
}
```

## Installation

Add the registry, once per project:

```bash
npx shadcn@latest registry add @jarvis=https://ui.jarv.is/r/{name}.json
```

Then add the component:

```bash
npx shadcn@latest add @jarvis/toolbar
```

## Usage

```tsx
import { BoldIcon, ItalicIcon, LinkIcon } from "lucide-react";

import {
  Toolbar,
  ToolbarButton,
  ToolbarSeparator,
  ToolbarToggle,
  ToolbarToggleGroup,
} from "@/components/ui/toolbar";

export function FormattingToolbar() {
  return (
    <Toolbar aria-label="Formatting">
      <ToolbarToggleGroup multiple aria-label="Text style">
        <ToolbarToggle value="bold" aria-label="Bold">
          <BoldIcon />
        </ToolbarToggle>
        <ToolbarToggle value="italic" aria-label="Italic">
          <ItalicIcon />
        </ToolbarToggle>
      </ToolbarToggleGroup>
      <ToolbarSeparator />
      <ToolbarButton size="icon" aria-label="Add link">
        <LinkIcon />
      </ToolbarButton>
    </Toolbar>
  );
}
```

Use a toolbar for controls that act on the same thing, such as the selection in an editor. Tab
reaches it once, and the arrow keys move between its controls. Give it an `aria-label`.

Group toggles in a `ToolbarToggleGroup`, with `multiple` to allow more than one. `ToolbarGroup`
groups other controls, and `ToolbarSeparator` divides groups.

For buttons that each take a Tab stop, use a [Button Group](https://ui.jarv.is/components/button-group.md).

## Composition

- `Toolbar`
  - `ToolbarButton`
  - `ToolbarLink`
  - `ToolbarInput`
  - `ToolbarToggleGroup`
    - `ToolbarToggle`
  - `ToolbarGroup`
  - `ToolbarSeparator`

## Examples

### With tooltips

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

```tsx
import { CopyIcon, Redo2Icon, ScissorsIcon, Undo2Icon } from "lucide-react";

import { Kbd, KbdGroup } from "@/components/ui/kbd";
import { Toolbar, ToolbarButton, ToolbarSeparator } from "@/components/ui/toolbar";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";

const history = [
  { label: "Undo", keys: ["⌘", "Z"], icon: Undo2Icon },
  { label: "Redo", keys: ["⇧", "⌘", "Z"], icon: Redo2Icon },
];

const clipboard = [
  { label: "Cut", keys: ["⌘", "X"], icon: ScissorsIcon },
  { label: "Copy", keys: ["⌘", "C"], icon: CopyIcon },
];

function Action({ label, keys, icon: Icon }: (typeof history)[number]) {
  return (
    <Tooltip>
      <TooltipTrigger render={<ToolbarButton size="icon" aria-label={label} />}>
        <Icon />
      </TooltipTrigger>
      <TooltipContent>
        {label}
        <KbdGroup>
          {keys.map((key) => (
            <Kbd key={key}>{key}</Kbd>
          ))}
        </KbdGroup>
      </TooltipContent>
    </Tooltip>
  );
}

export default function ToolbarWithTooltips() {
  return (
    <Toolbar aria-label="Edit">
      {history.map((action) => (
        <Action key={action.label} {...action} />
      ))}
      <ToolbarSeparator />
      {clipboard.map((action) => (
        <Action key={action.label} {...action} />
      ))}
    </Toolbar>
  );
}
```

### With a menu

Render a menu's trigger through a `ToolbarButton`'s `render` prop to keep it in the arrow key order.
`ToolbarLink` adds a link.

```tsx
import {
  ChevronDownIcon,
  ImageIcon,
  MessageSquarePlusIcon,
  MinusIcon,
  TableIcon,
} from "lucide-react";

import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Toolbar, ToolbarButton, ToolbarLink, ToolbarSeparator } from "@/components/ui/toolbar";
import { preventNavigation } from "@/examples/data";

export default function ToolbarWithMenu() {
  return (
    <Toolbar aria-label="Document" className="w-full max-w-md">
      <DropdownMenu>
        <ToolbarButton render={<DropdownMenuTrigger />} className="pr-1.5">
          Insert
          <ChevronDownIcon />
        </ToolbarButton>
        <DropdownMenuContent>
          <DropdownMenuItem>
            <ImageIcon />
            Image
          </DropdownMenuItem>
          <DropdownMenuItem>
            <TableIcon />
            Table
          </DropdownMenuItem>
          <DropdownMenuItem>
            <MinusIcon />
            Divider
          </DropdownMenuItem>
        </DropdownMenuContent>
      </DropdownMenu>
      <ToolbarSeparator />
      <ToolbarButton>
        <MessageSquarePlusIcon />
        Comment
      </ToolbarButton>
      <ToolbarLink href="#" onClick={preventNavigation} className="ml-auto">
        Edited 2m ago
      </ToolbarLink>
    </Toolbar>
  );
}
```

### With an input

`ToolbarInput` adds a text field. Use at most one, and put it last, since the arrow keys also move
its cursor.

```tsx
import { ArrowUpDownIcon, ListFilterIcon } from "lucide-react";

import { Toolbar, ToolbarButton, ToolbarInput, ToolbarSeparator } from "@/components/ui/toolbar";

export default function ToolbarWithInput() {
  return (
    <Toolbar aria-label="Issues">
      <ToolbarButton>
        <ListFilterIcon />
        Filter
      </ToolbarButton>
      <ToolbarButton>
        <ArrowUpDownIcon />
        Sort
      </ToolbarButton>
      <ToolbarSeparator />
      <ToolbarInput type="search" placeholder="Search issues…" aria-label="Search issues" />
    </Toolbar>
  );
}
```

### Vertical

Set `orientation="vertical"` to stack the controls.

```tsx
import {
  CircleIcon,
  HandIcon,
  MousePointer2Icon,
  PencilIcon,
  SquareIcon,
  TypeIcon,
} from "lucide-react";

import {
  Toolbar,
  ToolbarSeparator,
  ToolbarToggle,
  ToolbarToggleGroup,
} from "@/components/ui/toolbar";

export default function ToolbarVertical() {
  return (
    <Toolbar orientation="vertical" aria-label="Tools">
      <ToolbarToggleGroup defaultValue={["select"]} aria-label="Tool">
        <ToolbarToggle value="select" aria-label="Select">
          <MousePointer2Icon />
        </ToolbarToggle>
        <ToolbarToggle value="pan" aria-label="Pan">
          <HandIcon />
        </ToolbarToggle>
        <ToolbarSeparator />
        <ToolbarToggle value="rectangle" aria-label="Rectangle">
          <SquareIcon />
        </ToolbarToggle>
        <ToolbarToggle value="ellipse" aria-label="Ellipse">
          <CircleIcon />
        </ToolbarToggle>
        <ToolbarToggle value="pencil" aria-label="Pencil">
          <PencilIcon />
        </ToolbarToggle>
        <ToolbarToggle value="text" aria-label="Text">
          <TypeIcon />
        </ToolbarToggle>
      </ToolbarToggleGroup>
    </Toolbar>
  );
}
```

## Props

| Part | Extends |
| --- | --- |
| `Toolbar` | [`Toolbar.Root`](https://base-ui.com/react/components/toolbar#root) |
| `ToolbarGroup` | [`Toolbar.Group`](https://base-ui.com/react/components/toolbar#group) |
| `ToolbarButton` | [`Toolbar.Button`](https://base-ui.com/react/components/toolbar#button) |
| `ToolbarToggleGroup` | [`ToggleGroup`](https://base-ui.com/react/components/toggle-group#api-reference) |
| `ToolbarToggle` | [`Toolbar.Button`](https://base-ui.com/react/components/toolbar#button) |
| `ToolbarSeparator` | [`Toolbar.Separator`](https://base-ui.com/react/components/toolbar#separator) |
| `ToolbarLink` | [`Toolbar.Link`](https://base-ui.com/react/components/toolbar#link) |
| `ToolbarInput` | [`Toolbar.Input`](https://base-ui.com/react/components/toolbar#input) |

The tables leave out `className`, `style` and `render`. Parts built on Base UI take them as its [styling](https://base-ui.com/react/handbook/styling) and [composition](https://base-ui.com/react/handbook/composition) guides describe.

### Toolbar

| Prop           | Type                         | Description                                                                                                                   |
| -------------- | ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `disabled?`    | `boolean`                    |                                                                                                                               |
| `orientation?` | `"horizontal" \| "vertical"` | The orientation of the toolbar. Default: `'horizontal'`                                                                       |
| `loopFocus?`   | `boolean`                    | If `true`, using keyboard navigation will wrap focus to the other end of the toolbar once the end is reached. Default: `true` |

### ToolbarGroup

| Prop        | Type      | Description                                                               |
| ----------- | --------- | ------------------------------------------------------------------------- |
| `disabled?` | `boolean` | When `true` all toolbar items in the group are disabled. Default: `false` |

### ToolbarButton

| Prop                     | Type                  | Description                                                                                                                                                                                       |
| ------------------------ | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `disabled?`              | `boolean`             | When `true` the item is disabled. Default: `false`                                                                                                                                                |
| `focusableWhenDisabled?` | `boolean`             | When `true` the item remains focusable when disabled. Default: `true`                                                                                                                             |
| `nativeButton?`          | `boolean`             | Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `false` if the rendered element is not a button (for example, `<div>`). Default: `true` |
| `size?`                  | `"default" \| "icon"` | Default: `"default"`                                                                                                                                                                              |

### ToolbarToggleGroup

| Prop             | Type                                                                             | Description                                                                                                                                                                                     |
| ---------------- | -------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `value?`         | `readonly string[]`                                                              | The pressed state of the toggle group represented by an array of the values of all pressed toggle buttons. This is the controlled counterpart of `defaultValue`.                                |
| `defaultValue?`  | `readonly string[]`                                                              | The pressed state of the toggle group represented by an array of the values of all pressed toggle buttons. This is the uncontrolled counterpart of `value`.                                     |
| `onValueChange?` | `((groupValue: string[], eventDetails: ToggleGroup.ChangeEventDetails) => void)` | Callback fired when the pressed states of the toggle group changes.                                                                                                                             |
| `disabled?`      | `boolean`                                                                        | Whether the toggle group should ignore user interaction. Default: `false`                                                                                                                       |
| `orientation?`   | `"horizontal" \| "vertical"`                                                     | Default: `'horizontal'`                                                                                                                                                                         |
| `loopFocus?`     | `boolean`                                                                        | Whether to loop keyboard focus back to the first item when the end of the list is reached while using the arrow keys. Default: `true`                                                           |
| `multiple?`      | `boolean`                                                                        | When `false` only one item in the group can be pressed. If any item in the group becomes pressed, the others will become unpressed. When `true` multiple items can be pressed. Default: `false` |

### ToolbarToggle

| Prop                     | Type                                                                    | Description                                                                                                                                                                                       |
| ------------------------ | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `nativeButton?`          | `boolean`                                                               | Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `false` if the rendered element is not a button (for example, `<div>`). Default: `true` |
| `disabled?`              | `boolean`                                                               | When `true` the item is disabled. Default: `false`                                                                                                                                                |
| `focusableWhenDisabled?` | `boolean`                                                               | When `true` the item remains focusable when disabled. Default: `true`                                                                                                                             |
| `pressed?`               | `boolean`                                                               | Whether the toggle button is currently pressed. This is the controlled counterpart of `defaultPressed`.                                                                                           |
| `defaultPressed?`        | `boolean`                                                               | Whether the toggle button is currently pressed. This is the uncontrolled counterpart of `pressed`. Default: `false`                                                                               |
| `onPressedChange?`       | `((pressed: boolean, eventDetails: Toggle.ChangeEventDetails) => void)` | Callback fired when the pressed state is changed.                                                                                                                                                 |
| `value?`                 | `string`                                                                | A unique string that identifies the toggle when used inside a toggle group.                                                                                                                       |
| `size?`                  | `"default" \| "icon"`                                                   | Default: `"icon"`                                                                                                                                                                                 |

### ToolbarSeparator

| Prop           | Type                         | Description                                                                                                                                   |
| -------------- | ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `orientation?` | `"horizontal" \| "vertical"` | The orientation of the separator. Defaults to the opposite of the toolbar's orientation, so a horizontal toolbar renders vertical separators. |

### ToolbarInput

| Prop                     | Type                                    | Description                                                           |
| ------------------------ | --------------------------------------- | --------------------------------------------------------------------- |
| `disabled?`              | `boolean`                               | When `true` the item is disabled. Default: `false`                    |
| `focusableWhenDisabled?` | `boolean`                               | When `true` the item remains focusable when disabled. Default: `true` |
| `defaultValue?`          | `string \| number \| readonly string[]` |                                                                       |
