Cloud|

@k2b/ui

Toolbar

Semantic action groups, floating selection actions, priority overflow, and keyboard hints.

Toolbar@k2b/ui

A semantic action row with named groups, separators, a flexible spacer, and optional wrapping. One Tab stop, orientation-aware arrow keys, and Home/End navigate rendered controls.

TSX

Copy
<Toolbar label="Document actions" wrap>
<Toolbar.Group label="History">
<IconButton size="xs" label="Undo"><i class="ti ti-arrow-back-up" /></IconButton>
<IconButton size="xs" label="Redo"><i class="ti ti-arrow-forward-up" /></IconButton>
</Toolbar.Group>
<Toolbar.Separator />
<Button size="xs" variant="subtle"><i class="ti ti-activity" /> Status</Button>
<Toolbar.Spacer />
<Button size="sm">Publish</Button>
</Toolbar>
Toolbar@k2b/ui

Selection actions stay at the column bottom. Switch viewport widths to move lower-priority actions into More; the message rows keep their full scroll space.

Inbox

Message 1
Message 2
Message 3
Message 4
Message 5
Message 6
Message 7
Message 8
Message 9
Message 10
Message 11
Message 12
Message 13
Message 14
Message 15
Message 16
Message 17
Message 18
Message 19
Message 20
Message 21
Message 22
Message 23
Message 24
Message 25
Message 26
Message 27
Message 28
Message 29
Message 30
Select an action

TSX

Copy
const [width, setWidth] = createSignal("1194");
const [lastAction, setLastAction] = createSignal("Select an action");
const widths: Record<string, number> = { "1194": 575, "834": 437, "694": 340 };
const actions = [
{ label: "Reply", icon: "ti ti-arrow-back-up", priority: 2, keys: "r" },
{ label: "Forward", icon: "ti ti-arrow-forward-up", priority: 7, keys: "f" },
{ label: "Archive", icon: "ti ti-archive", priority: 1, keys: "e" },
{ label: "Later", icon: "ti ti-clock", priority: 3, keys: "h" },
{ label: "Move", icon: "ti ti-folder", priority: 4, keys: "v" },
{ label: "Delete", icon: "ti ti-trash", priority: 6, keys: "delete" },
{ label: "Assistant", icon: "ti ti-sparkles", priority: 5, keys: "mod+j" },
];
<SegmentedControl
ariaLabel="Viewport width"
value={width}
onValueChange={setWidth}
options={["1194", "834", "694"].map((value) => ({ value, label: value }))}
/>
<div style={{ display: "flex", "flex-direction": "column", width: `${widths[width()]}px`, "max-width": "100%", height: "380px" }}>
<h3>Inbox</h3>
<div style={{ flex: "1", "min-height": "0", overflow: "auto" }}>
<For each={Array.from({ length: 30 }, (_, index) => index + 1)}>
{(row) => <div style={{ height: "44px", padding: "0.75rem" }}>Message {row}</div>}
</For>
</div>
<Toolbar label="Selection actions" variant="floating">
<Toolbar.Label>3 selected</Toolbar.Label>
<For each={actions.slice(0, 2)}>
{(action) => <Toolbar.Action {...action} onClick={() => setLastAction(action.label)} />}
</For>
<Toolbar.Separator />
<For each={actions.slice(2)}>
{(action) => <Toolbar.Action {...action} onClick={() => setLastAction(action.label)} />}
</For>
<Toolbar.Separator />
<Toolbar.Action label="Done" icon="ti ti-check" keys="escape" onClick={() => setLastAction("Done")} />
</Toolbar>
</div>
<output aria-live="polite">{lastAction()}</output>

Toolbar gives related actions one semantic and responsive container.

Use Toolbar

Use it for editor commands, table actions, and compact page controls. Use Toolbar.Group for named clusters, Toolbar.Separator between clusters, and Toolbar.Spacer before a trailing primary action.

Import

tsx
import { Button, IconButton, Toolbar } from "@k2b/ui";

Layout

Enable wrap where actions may outgrow the available width. Button variants remain yours.

Separators automatically follow the toolbar orientation: they are vertical in a horizontal toolbar and horizontal in a vertical toolbar.

Floating variant

Use variant="floating" for selection actions at the bottom of a column. It is always horizontal and never wraps. Its buttons have 44 × 44 px targets.

Place it last in a flex column: a heading, a scroll area with flex: 1 and min-height: 0, then the toolbar. The column reserves the toolbar's height, keeping the final row clear. It can also be the last child inside a scroll container: the strip sticks to the bottom and reserves its own height after the rows. It never leaves its column: when the column is taller than the screen, the toolbar stays at the bottom of the visible part of the column.

Render it in an island. The browser measures it before its first paint, so it never rearranges after it appears; until then it is invisible but keeps its space. A toolbar under a hidden ancestor waits until it is shown. Without scripting, the server layout stays visible.

Toolbar.Action

Use Toolbar.Action for ghost icon buttons. label names the button, its tooltip, and its row in the More menu; icon takes a class such as "ti ti-archive". disabled applies to both the button and its menu row. onClick runs from either place.

In a floating toolbar, priority lets an action move into More when the column is too narrow. 1 is most important. Larger numbers move first; equal priorities move the later action first. Omit priority to keep an action fixed. More is the last control and lists the moved actions in their original order. Widening the column brings them back. Inline toolbars keep every action in place; use wrap there.

keys uses the Kbd format, such as "e", "mod+j", or "shift+arrowdown". It adds a hint under the icon, the platform shortcut to the tooltip, and aria-keyshortcuts. Hints appear with the keyboard input flag and never move icons or resize buttons. The application still owns shortcut handlers.

To hide an action, stop rendering it, for example with <Show>. A Toolbar.Group hidden through its class also takes its actions out of More, because the toolbar measures again. A style that an outer element's class applies inside the toolbar is noticed only at the next resize. In a floating toolbar, empty groups and leading, trailing, or repeated separators are hidden. Keep fixed content compact. Actions move before a Toolbar.Label truncates; only when the fixed items alone are too wide does the label shrink.

Toolbar.Label

Toolbar.Label is non-interactive text for a selection count or event title. It never moves into More. It stays on one line, can shrink, and truncates with an ellipsis at about 10rem.

Accessibility

Always provide label. Name groups when their purpose is not obvious.

The roving group has one Tab stop: the last focused item, or the first item initially. Left/Right move and wrap in a horizontal toolbar (reversed in RTL); Up/Down do the same in a vertical toolbar. Home and End focus the first and last items. Modified keys and keys in menus keep their own behavior.

Roving items are buttons, links with href, checkboxes and other button-like inputs, and controls with tabindex. Text fields, textareas, selects, sliders, radio buttons, and editable regions stay their own Tab stops. They keep their arrow, Home, and End keys.

Hidden and natively disabled controls are skipped, as are controls inside menus, listboxes, dialogs, or popovers. The tab stop follows visibility. A toolbar that becomes visible later, or whose tab stop is hidden, picks the first visible control. aria-disabled="true" controls remain reachable; their handlers must guard unavailable actions.

More is the last roving control. Enter opens its menu; menu arrows navigate its rows. Escape closes the menu and returns focus to More. If a focused action moves into More after a resize, focus follows it to the More trigger.

Set aria-pressed="true" or "false" on toggle buttons. Pressed buttons use the action colour and selected background without changing their size or position. Forced colours use HighlightText on Highlight.

Runtime

The layout renders on the server without roving tabindex attributes. Keyboard navigation and child action handlers require browser code.

Example

tsx
<Toolbar label="Document actions" wrap>
  <Toolbar.Group label="History">
    <IconButton label="Undo"><i class="ti ti-arrow-back-up" /></IconButton>
    <IconButton label="Redo"><i class="ti ti-arrow-forward-up" /></IconButton>
  </Toolbar.Group>
  <Toolbar.Separator />
  <Button size="xs" variant="subtle">Status</Button>
  <Toolbar.Spacer />
  <Button size="sm">Publish</Button>
</Toolbar>

Example: mail selection

tsx
<div style={{ display: "flex", "flex-direction": "column", height: "600px" }}>
  <h2>Inbox</h2>
  <div style={{ flex: "1", "min-height": "0", overflow: "auto" }}>
    {/* Message rows */}
  </div>
  <Toolbar label="Selection actions" variant="floating">
    <Toolbar.Label>3 selected</Toolbar.Label>
    <Toolbar.Action label="Reply" icon="ti ti-arrow-back-up" priority={2} keys="r" onClick={reply} />
    <Toolbar.Action label="Forward" icon="ti ti-arrow-forward-up" priority={7} keys="f" onClick={forward} />
    <Toolbar.Separator />
    <Toolbar.Action label="Archive" icon="ti ti-archive" priority={1} keys="e" onClick={archive} />
    <Toolbar.Action label="Later" icon="ti ti-clock" priority={3} keys="h" onClick={later} />
    <Toolbar.Action label="Move" icon="ti ti-folder" priority={4} keys="v" onClick={move} />
    <Toolbar.Action label="Delete" icon="ti ti-trash" priority={6} keys="delete" onClick={remove} />
    <Toolbar.Action label="Assistant" icon="ti ti-sparkles" priority={5} keys="mod+j" onClick={assistant} />
    <Toolbar.Separator />
    <Toolbar.Action label="Done" icon="ti ti-check" keys="escape" onClick={clearSelection} />
  </Toolbar>
</div>

The application provides the message rows and action handlers. Done and the selection count stay in the toolbar at every width.

Connect a coding agent

The Cloud skill provides compact working instructions. MCP supplies exact current documentation when details matter.

CLI command
bunx skills add https://docs.example.com

This command installs the working instructions published by this website in the selected coding agent.

For exact, current details, also connect the MCP server through one of the agent tabs.

Installation uses the open-source Vercel Skills CLI.

The skill and MCP complement each other: the skill describes workflows, while MCP supplies current documentation.