Block Toolbar
The one place a block's own controls sit — its top-right corner, 28px icon buttons, at most four and a More menu for the rest.
- Status
- Since
0.25.89- Accessibility pattern
- a named group; every button named and tipped by its label
Last updated
Install
Configure registry credentials first using the Quickstart. Before copying this item or any transitive registry dependency, verify the signed manifest and save the exact bytes for Block Toolbar before installation. Retain the digest printed by this command.
pnpm exec vegastack-design verify block-toolbarInstall with the pinned shadcn CLI. Bare shadcn does not verify VegaStack signature or integrity.
pnpm dlx shadcn@4.21.0 add @vegastack/block-toolbarRun the exact offline --post-write command printed by the preflight, using its saved item path and independently retained --expected-integrity digest. Use the saved preflight for every transitive registry dependency too; stop on any mismatch before using the components.
The same command installs the registry items it composes: @vegastack/button, @vegastack/dropdown-menu, @vegastack/tooltip.
Usage
import {
BlockToolbar,
BlockToolbarButton,
BlockToolbarMenu,
} from "@/components/ui/block-toolbar";
<figure className="group/block relative">
<BlockToolbar aria-label="Image actions" reveal="hover">
<BlockToolbarButton label="Caption" onClick={editCaption}>
<Captions />
</BlockToolbarButton>
<BlockToolbarMenu label="More image actions">
<DropdownMenuItem onClick={copyLink}>
<Link />
Copy link
</DropdownMenuItem>
</BlockToolbarMenu>
</BlockToolbar>
…
</figure>;Every block with chrome puts its controls in the same corner at the same size: a code block's
wrap and copy, an image's replace, caption, alt text and open, a diagram's expand and actions. At
most four buttons show; whatever else the block can do goes in the ⋯ menu. Buttons are 28px ghost
icon buttons named by label, which is also their tooltip, and a press never takes focus from an
editor the block sits in. The toolbar never prints.
Scope
- Owns: the corner, the 28px buttons and their tooltips, the ⋯ menu, the hover reveal.
- Does not own: what the controls do — they are the block's — or the block's own header.
- Compose with:
CopyButton(size="icon-sm") andToggle(className="size-7") as children when a control is one of those.
Anatomy
Examples
Shown on hover
reveal="hover" hides the toolbar until the block — an ancestor with the group/block class —
is hovered or holds focus, and keeps it shown while its menu is open. A coarse pointer, which has
no hover, always shows it.
Always shown
The default: a code block's copy is always there. placement="inline" drops the corner position
for a block that already has a header row (a diagram's).
API Reference
BlockToolbar
| Prop | Type | Default | Description |
|---|---|---|---|
aria-label* | string | — | What the controls are for ("Code block actions"): the group's accessible name. |
placement | "corner" | "inline" | "corner" | Where it sits: corner floats over the block's top-right corner (absolute — the block is
relative); inline sits in a row the block already has (a header). |
reveal | "always" | "hover" | "always" | When it shows: always, or while the block (an ancestor with the group/block class) is
hovered or holds focus — and always on a coarse pointer, which has no hover. |
Data attributes and CSS variables on BlockToolbar
| Attribute | Values |
|---|---|
data-placement | mirrors a prop or state value |
data-reveal | mirrors a prop or state value |
data-slot | "block-toolbar" |
BlockToolbarButton
| Prop | Type | Default | Description |
|---|---|---|---|
label* | string | — | The button's accessible name and tooltip. |
loading | boolean | — | Shows a spinner over the label, blocks activation and sets aria-busy. The label keeps its
box at opacity: 0, so the button's width does not move and its accessible name survives
(API-5, A11Y-12). |
Data attributes and CSS variables on BlockToolbarButton
| Attribute | Values |
|---|---|
data-slot | "block-toolbar-button" |
BlockToolbarMenu
| Prop | Type | Default | Description |
|---|---|---|---|
children* | React.ReactNode | — | The menu's items: DropdownMenuItems. |
label* | string | — | The ⋯ button's accessible name ("More image actions"). |
onOpenChange | ((open: boolean) => void) | — | Called when the menu opens or closes. |
Data attributes and CSS variables on BlockToolbarMenu
| Attribute | Values |
|---|---|
data-slot | "block-toolbar-menu" |
Accessibility
- The toolbar is a group named by
aria-label("Image actions"). - Every button is named by its
label, shown as its tooltip on hover and focus. - A hover-revealed toolbar shows while anything in the block holds focus, so the keyboard reaches it.
| Key | Action |
|---|---|
| Tab | Moves through the buttons |
| Enter / Space | Presses a button or opens ⋯ |
| ArrowUp / ArrowDown | Moves through the open menu |
| Escape | Closes the menu |
| Contract | States tested |
|---|---|
| Behaviour | default, hover-reveal, inline, open |
| Accessibility | labeled |
| Visual | default, hover-reveal, inline |