Skip to content
Component installs need the registry setup
VegaStack Design

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
stable
Since
0.25.89
Accessibility pattern
a named group; every button named and tipped by its label

Last updated

An image block

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-toolbar

Install with the pinned shadcn CLI. Bare shadcn does not verify VegaStack signature or integrity.

pnpm dlx shadcn@4.21.0 add @vegastack/block-toolbar

Run 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") and Toggle (className="size-7") as children when a control is one of those.

Anatomy

BlockToolbar — data-slot="block-toolbar"
BlockToolbarButton — data-slot="block-toolbar-button"
BlockToolbarMenu — data-slot="block-toolbar-menu"

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.

An image block

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).

const answer = 42;

API Reference

BlockToolbar

PropTypeDefaultDescription
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

AttributeValues
data-placementmirrors a prop or state value
data-revealmirrors a prop or state value
data-slot"block-toolbar"

BlockToolbarButton

PropTypeDefaultDescription
label*string—The button's accessible name and tooltip.
loadingboolean—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

AttributeValues
data-slot"block-toolbar-button"

BlockToolbarMenu

PropTypeDefaultDescription
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

AttributeValues
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.
KeyAction
TabMoves through the buttons
Enter / SpacePresses a button or opens ⋯
ArrowUp / ArrowDownMoves through the open menu
EscapeCloses the menu
ContractStates tested
Behaviourdefault, hover-reveal, inline, open
Accessibilitylabeled
Visualdefault, hover-reveal, inline

Do / Don't

Do
Put a block's controls in its top-right corner at 28px, four at most, the rest in ⋯.
Don't
Give each block its own chrome — a label on the left, 24px icons on the right, six of them in a row.

On this page