Overlays and menus

Selection Toolbar

A contextual toolbar for actions on selected text.

⠋

Install

npx shadcn@latest add https://4af.selfsimilar.dev/r/selection-toolbar.json

Usage

import { SelectionToolbar } from "@/components/ui/selection-toolbar";

<SelectionToolbar actions={<Button>Archive</Button>}>
  Selectable content
</SelectionToolbar>

API

Main exports: SelectionToolbar, SelectionToolbarButton, SelectionToolbarActionButton, SelectionToolbarToggle, SelectionToolbarActionToggle, SelectionToolbarToggleGroup, SelectionToolbarSeparator, useSelectionToolbar

A bar of actions over selected text, styled like a tooltip. A Base UI Popover anchors it to the selected range, so it follows the text while scrolling, and Base UI Toolbar handles arrow-key focus. Pressing an item doesn't collapse the selection, and every action receives the captured text and range.

Items are 24px high with 6px corners inside the toolbar's 8px shell, including icon-only forms.

Item kinds

The items match the menu's row types, without nested panels. Each is built on the matching library component.

ItemBuilt onDefault dismissal
SelectionToolbarButtonButtonDismisses; keepOpen opts out
SelectionToolbarActionButtonAction ButtonStays; closeOnSuccess opts in
SelectionToolbarToggleToggleStays open
SelectionToolbarActionToggleAction ToggleStays open
SelectionToolbarToggleGroupBase UI ToggleGroup in Toolbar.Group—
SelectionToolbarSeparatorBase UI Toolbar.Separator—

Every action receives the selection as it was when the bar opened: onAction(text, range), action(text, range), or onAction(pressed, text, range). Use these instead of window.getSelection(), which may have changed by the time the handler runs.

variant="destructive" uses red text on a neutral hover rather than a solid red fill, like the menu's destructive row.

For state derived from the active range, render a small child component and call useSelectionToolbar() inside it:

function HighlightToggle() {
  const { range } = useSelectionToolbar();
  return <SelectionToolbarToggle pressed={isHighlighted(range)} … />;
}

On this page