Toolbar

Compact action bar for grouping buttons, toggles, and helpers.

Installation
$Terminal
npx shadcn@latest add https://sdk-components.thesqd.com/r/toolbar.json
Usage
Group `ToolbarItem`s inside `ToolbarGroup` and divide them with `ToolbarSeparator`.
TSImport
import { Toolbar } from "@/components/ui/toolbar";
TSExample
import { LinkIcon, Trash2Icon } from "lucide-react";
import { Toolbar } from "@/components/ui/toolbar";

export function Example() {
  return (
    <Toolbar
      items={[
        { type: "button", icon: LinkIcon, label: "Link" },
        { type: "separator" },
        { type: "button", icon: Trash2Icon, label: "Delete", variant: "destructive" },
      ]}
      aria-label="Document actions"
    />
  );
}
Composition
Anatomy of the Toolbar primitive.
Toolbar (props)
├── items: ToolbarItem[]
│   ├── { type: "button",   label?, icon?, iconOnly?, variant?, pressed?, onClick?, ariaLabel?, disabled? }
│   ├── { type: "separator" }
│   ├── { type: "label",    content }
│   ├── { type: "spacer" }                  // pushes the NEXT renderable item to the trailing edge
│   └── { type: "node",     content }       // arbitrary ReactNode escape hatch
├── orientation?: "horizontal" | "vertical"
├── pill?: boolean                          // rounded-full ends
├── floating?: boolean                      // fixed-position floating bar
├── position?: "bottom-center" | "top-center" | "bottom-right" | "bottom-left"
├── visible?: boolean                       // when floating, drives slide+fade transition
└── aria-label?: string

ExpandableTabs (props)                       // dynamic-island bar; active tab expands to a labelled pill
├── tabs: ExpandableTab[]                     // { id, label, icon, content }
│   ├── id: string                            // stable id (also the active value)
│   ├── label: string                         // shown in the active pill + a11y name
│   ├── icon: ReactNode                       // icon node (e.g. <FileTextIcon className="size-4" />)
│   └── content: ReactNode                    // panel revealed above the bar
├── value? / defaultValue?: string | null     // active id (controlled / uncontrolled)
├── onValueChange?: (id | null) => void
├── classNames?: { root, panel, bar, tab, activeTab, icon, label, pill }
└── aria-label?: string
Default
Mixed buttons grouped with separators. Drop an `items` array; the renderer handles spacing and dividers.
Pill
Pass `pill` for rounded-full ends. Good for floating contextual toolbars (Notion-style block menus, image hovers).
Toggle items
Pass `pressed` on a button item to render an active toggle state with `aria-pressed`. Drive it with local state for one-of-many groups or boolean toggles.
Expandable tabs
A dynamic-island icon bar: the active tab expands into a labelled pill while the whole shell animates its width/height open to reveal that tab's `content` panel above the bar; content cross-fades on switch. Pass a `tabs` array of `{ id, label, icon, content }`; single-select, and clicking the active tab again closes it.
Inline bulk-action toolbar
Wire `<DataGrid onSelectionChange>` to local state, then render the toolbar above the table when the selection set is non-empty. The `clear` button re-mounts the grid via `key` to reset internal selection.
Brand refreshactiveAvery
Q4 micrositeactiveJordan
Onboarding rewritepausedSam
Annual reportactiveRiley
Investor deckarchivedCasey
5 total row(s).
Floating bulk-action toolbar
Pass `floating` for a fixed-position bar that anchors to `bottom-center` of the viewport (overridable via `position`). Drive `visible` from selection state to slide it in and out with a built-in transition.

Select one or more rows — a floating bulk-action toolbar slides up from the bottom of the viewport.

Brand refreshactiveAvery
Q4 micrositeactiveJordan
Onboarding rewritepausedSam
Annual reportactiveRiley
Investor deckarchivedCasey
5 total row(s).
Expanding panel
Give the toolbar a `panelId` and open it with `toolbarPanel.show(id, { icon, title, caption })` — from its own button or from anywhere else in the app, as the buttons under this one do. A sheet in a different shade slides up and a little wider from behind the bar (transitions-dev card resize) while the bar keeps its own container; the avatar, text and caption rise in turn with the texts reveal. Showing again swaps the message, `duration` auto-closes, and `useToolbarPanel(id)` keeps the trigger's pressed state in step.
API Reference
Props for Toolbar and ToolbarItem.

Toolbar

PropTypeDefaultDescription
itemsToolbarItem[]—Array of items rendered in order. Drives the entire bar — no compound subcomponents.
orientation"horizontal" | "vertical""horizontal"Layout direction; flips separators and gap axis.
pillbooleanfalseRounded-full container.
floatingbooleanfalseFixed-position floating bar with safe-area padding. Defaults to bottom-center.
position"bottom-center" | "top-center" | "bottom-right" | "bottom-left""bottom-center"Anchor point when `floating`.
visiblebooleantrueWhen `floating`, controls slide-up + fade transition. Toggle from selection state.
zIndexnumber50Stack-context layer when `floating`.
panelReactNode—Custom content above the button row while `expanded`. The bar keeps its own container; a sheet in a different shade (`bg-muted`) slides out from behind it — up, and 12px wider per side or to the panel's width — and the bar widens with it to the same width, its button groups staying tight while the extra width opens up around each divider (75%) and past the first and last item (12.5% each end) — both with the transitions-dev card resize, uncovering the panel from its bottom edge. The layout box stays the button row's, so nothing around it shifts: leave headroom above. Tag lines with `t-stagger-line t-stagger-line--N` for the texts-reveal entrance. For a message, prefer `panelId` + `toolbarPanel`.
expandedbooleanfalseOpens `panel`. Controlled — flip it from a toolbar button (give that button `pressed` so it reads as a toggle). Collapsed, the panel is `inert`.
panelIdstring—Hooks this toolbar up to `toolbarPanel`, so anything in the app can open it and set what it says: `toolbarPanel.show(id, { icon?, title, caption? }, { duration? })` opens it (showing again swaps the message with a fresh reveal; `duration` closes it by itself), `toolbarPanel.hide(id)` closes it, and `useToolbarPanel(id)` returns the message on screen — for a trigger's `pressed`. The icon, title and caption rise in turn with the texts reveal; the message stays drawn while the sheet closes over it.
aria-labelstring—Accessibility label for the toolbar landmark.

Toolbar button item

PropTypeDefaultDescription
type"button"—Discriminator — always `"button"` for action items.
labelReactNode—Visible label. Omit when `iconOnly` is true.
iconLucideIcon—Optional leading icon component (lucide-react).
iconOnlybooleanfalseSquare 32×32 button. Always pair with `ariaLabel` for accessibility.
variant"ghost" | "primary" | "destructive""ghost"Tone. `primary` = filled Squad violet, `destructive` = red tonal, `ghost` = transparent.
pressedboolean—Toggle state. Renders with accented surface and `aria-pressed="true"`.
disabledboolean—Disables the button.
onClick(e) => void—Fires when the item is activated.
ariaLabelstring—Explicit aria-label (required when `iconOnly`).

ExpandableTabs

PropTypeDefaultDescription
tabsExpandableTab[]—Tabs in order. Each is `{ id, label, icon, content }` — `label` shows in the active pill, `icon` is a node, and `content` is the panel revealed above the bar.
valuestring | null—Controlled active tab id (`null` = closed). Pair with `onValueChange`.
defaultValuestring | nullnullUncontrolled initial active tab id.
onValueChange(id: string | null) => void—Fires with the new active id (or `null` when closed).
classNames{ root, panel, bar, tab, activeTab, icon, label, pill }—Per-slot className overrides for fine-grained styling.
aria-labelstring"Navigation tabs"Accessibility label for the tablist.