# Toolbar

A row (or column) of controls that is one tab stop: the arrow keys move between its buttons, links and fields, swapped in a right-to-left page, and Home and End jump to the first and the last. A control that can't be used now goes inside `Locked` with its reason and stays in the arrow order, focusable, so a keyboard user reaches it and hears why.

Its parts are `ToolbarGroup` (a named set), `ToolbarButton`, `ToolbarLink`, `ToolbarInput`, `ToolbarSeparator` and `ToolbarSpacer`, which pushes what follows it to the far end: put the actions that belong at the start in a group before it, and the rest in one after it.

For an artifact's maximise, download and ask actions use `ArtifactToolbar`, and a data table keeps its own toolbar; `Toolbar` is for any other set of actions over a document, a canvas or a list.

- Registry name: `toolbar` (`@candor/toolbar`)
- Tier: Pro. Installing it needs a Pro, Premium or Team licence key: the person runs `npx candor-ui key <key>` themselves; never ask for the key in chat or write it to a file.
- Docs: https://candor.themethodic.com/docs/toolbar

## Install

```bash
npx candor-ui add toolbar
```

Run `npx candor-ui@latest init` once first (https://candor.themethodic.com/get-started.md). Without the CLI: `npx shadcn@latest add @candor/toolbar`.
It brings in: button, locale, motion, tokens, use-announcer, utils.
npm packages: @base-ui/react@^1.9.0, class-variance-authority@^0.7.1.

## Example

```tsx
"use client"

import * as React from "react"
import { AlignCenter, AlignLeft, Bold, Italic, MessageSquare, Share2, Underline } from "lucide-react"
import { Locked } from "@/components/candor/locked"
import { TooltipProvider } from "@/components/candor/ui/tooltip"
import { Toolbar, ToolbarButton, ToolbarGroup, ToolbarInput, ToolbarLink, ToolbarSeparator, ToolbarSpacer } from "@/components/candor/ui/toolbar"

export default function Example() {
  const [last, setLast] = React.useState("Nothing yet.")
  // The app's selection: the "Selection" group shows only while there is one, and is announced when it appears.
  const [selected, setSelected] = React.useState(false)
  return (
    <TooltipProvider>
      <div className="flex max-w-2xl flex-col gap-2 rounded-xl border bg-card p-2">
        <Toolbar label="Formatting">
          <ToolbarGroup label="Text style">
            <ToolbarButton size="icon-sm" aria-label="Bold" onClick={() => setLast("Bold")}>
              <Bold aria-hidden />
            </ToolbarButton>
            <ToolbarButton size="icon-sm" aria-label="Italic" onClick={() => setLast("Italic")}>
              <Italic aria-hidden />
            </ToolbarButton>
            <Locked reason="Underline is off in shared notes.">
              <ToolbarButton size="icon-sm" aria-label="Underline">
                <Underline aria-hidden />
              </ToolbarButton>
            </Locked>
          </ToolbarGroup>
          <ToolbarSeparator />
          <ToolbarGroup label="Alignment">
            <ToolbarButton size="icon-sm" aria-label="Align left" onClick={() => setLast("Align left")}>
              <AlignLeft aria-hidden />
            </ToolbarButton>
            <ToolbarButton size="icon-sm" aria-label="Align centre" onClick={() => setLast("Align centre")}>
              <AlignCenter aria-hidden />
            </ToolbarButton>
          </ToolbarGroup>
          {selected ? (
            <>
              <ToolbarSeparator />
              <ToolbarGroup label="Selection" reveal>
                <ToolbarButton onClick={() => setLast("Comment")}>
                  <MessageSquare aria-hidden />
                  Comment
                </ToolbarButton>
              </ToolbarGroup>
            </>
          ) : null}
          <ToolbarSpacer />
          <ToolbarGroup label="Document">
            <ToolbarInput label="Find in note" placeholder="Find" />
            <ToolbarLink href="#toolbar">Help</ToolbarLink>
            <ToolbarButton onClick={() => setLast("Share")}>
              <Share2 aria-hidden />
              Share
            </ToolbarButton>
          </ToolbarGroup>
        </Toolbar>
        <label className="flex items-center gap-1.5 px-2 text-xs text-muted-foreground">
          <input type="checkbox" checked={selected} onChange={(e) => setSelected(e.target.checked)} />
          Text is selected
        </label>
        <p className="px-2 text-xs text-muted-foreground">{last}</p>
      </div>
    </TooltipProvider>
  )
}
```

## <Toolbar>

Props:

- `label: string`: The toolbar's accessible name ("Formatting", "Actions for Q3 report"). Required: a toolbar without one is a row of buttons with no name.
- `orientation?: "horizontal" | "vertical" | undefined` (default `"horizontal"`): Which way it runs: "horizontal" (the default, Left and Right move) or "vertical" (Up and Down move).
- `loopFocus?: boolean | undefined` (default `true`): Whether an arrow key at the last control wraps to the first, and back. On by default.
- `className?: string | undefined`: Classes for the root, merged with Candor's.
- `children?: React.ReactNode | undefined`: The parts: groups, buttons, links, fields, separators and a spacer.

Also takes the props of `Omit<ToolbarPrimitive.Root.Props, "className" | "render" | "aria-label" | "orientation" | "loopFocus">`.

## <ToolbarButton>

A button in a `Toolbar`, a ghost `Button` by default. Name it in `aria-label` when it shows only an icon. To lock it, wrap it in `Locked` with the reason: it stays in the arrow order.

Props:

- `variant?: VariantProps<typeof buttonVariants>["variant"] | undefined` (default `"ghost"`): The button's look, as `Button`'s: "ghost" by default.
- `size?: VariantProps<typeof buttonVariants>["size"] | undefined` (default `"sm"`): The button's size, as `Button`'s: "sm" by default; "icon-sm" for an icon alone.
- `className?: string | undefined`: Classes for the button, merged with Candor's.

Also takes the props of `Omit<ToolbarPrimitive.Button.Props, "className" | "render">`.

## <ToolbarGroup>

A named set of controls inside a `Toolbar` ("Text style", "Alignment"), read as one group by screen readers.

Set `reveal` on a group the app shows only sometimes, such as the actions for a selection: when it appears after the toolbar first drew, it fades in and screen readers hear once how many actions it brought ("3 actions available"). Render it only while it applies; one there at the first draw is still and quiet. When it goes with focus in it, focus moves to the toolbar's first control.

Props:

- `label?: string | undefined`: The group's accessible name. Without one it is still a group, unnamed.
- `reveal?: boolean | undefined` (default `false`): The group appears and goes with what it acts on (a selection): it fades in and is announced once when it appears.
- `className?: string | undefined`: Classes for the group, merged with Candor's.

Also takes the props of `Omit<ToolbarPrimitive.Group.Props, "className" | "render">`.

## <ToolbarInput>

A text field in a `Toolbar`. The arrow keys move the caret inside it, and leave it for the next control only from its first or last character, so typing is never cut short.

Props:

- `label: string`: The field's accessible name. A placeholder isn't one, so it is required.
- `className?: string | undefined`: Classes for the field, merged with Candor's.

Also takes the props of `Omit<ToolbarPrimitive.Input.Props, "className" | "render" | "aria-label">`.

## <ToolbarLink>

A link in a `Toolbar`, drawn as a ghost button, reached with the same arrow keys as the buttons beside it.

Props:

- `className?: string | undefined`: Classes for the link, merged with Candor's.

Also takes the props of `Omit<ToolbarPrimitive.Link.Props, "className">`.

## <ToolbarSeparator>

A line between two sets of controls in a `Toolbar`, across its direction; screen readers hear it as a separator. It is a border, so it stays drawn in forced colours.

Props:

- `className?: string | undefined`: Classes for the line, merged with Candor's.

Also takes the props of `Omit<ToolbarPrimitive.Separator.Props, "className" | "render">`.

## <ToolbarSpacer>

Empty space that pushes the controls after it to the toolbar's far end (the inline end, or the bottom when vertical).

Props:

- `className?: string | undefined`: Classes for the space, merged with Candor's.

## Strings (`toolbar`)

The fixed words, English by default. Override any of them under `toolbar` in `<LocaleProvider strings={{ toolbar: { … } }}>`; the table is exported as `toolbarStrings`.

- `revealed`: { one: "{count} action available", other: "{count} actions available" }

## Motion

- **group** (reveal, fade): A `reveal` group fades in when it appears after the toolbar first drew.

Set with `--cd-motion-toolbar-<moment>-*` in your brand file; the families are in the design reference.

## Rules for using Candor UI

- Components take plain typed data. Never pass model output as HTML or render it as a link; messages are typed blocks.
- A control that can't be used says why: wrap it in `<Locked reason="…">`, don't just disable it.
- Don't show a value you don't have: leave it out (it renders as —) rather than guess. An estimate is labelled as one.
- Nothing is pre-selected in a decision a person makes, and the agent is named from your app's data, never from model output.
- Your app's words go in `LocaleProvider`: its fixed strings in `strings`, its nouns in `nouns`; entity kinds and agent roles in `EntityKindsProvider` and `AgentRolesProvider`.
