Components / structure

Toolbar

Forty-one of the toolbar's forty-six upstream refs test a menu button, a spinbutton and a radio group inside the toolbar. They are another pattern's contract, recorded as not applicable rather than ported twice.

Rendered from the package · open on its own

Summary

A group of controls sharing one tab stop, with arrow keys moving inside it.

APG patternToolbar
Built onnative elements
Statusstable
Since0.1.0
<Toolbar label="Formatting">…</Toolbar>

Anatomy

A <div data-se="toolbar" role="toolbar"> holding controls and optional separators.

PartElementRequired
groupthe caller'sthe caller's
separator<div>yes

Properties

PropertyValuesDefaultWhat it does
labelstringrequiredRequired — a toolbar with no name is an unlabelled region.
orientationhorizontal · verticalhorizontalWhich way the controls run, and therefore which arrow keys move between them. Announced as aria-orientation, so a screen reader tells the reader which keys to try.

States

_None. Rest is the only condition it has._

Behavior

That single tab stop is the entire point. A toolbar of twelve buttons should cost a keyboard user one Tab, not twelve. A row of buttons in a plain div looks identical and is not a toolbar.

Roving focus is computed, not registered. The toolbar finds its items by looking for whatever is focusable inside it, so a composite child — a radio group, a menu button — is one item rather than invisible.

Arrows clamp at the ends rather than wrapping. The APG marks wrapping optional and this one clamps.

Accessibility

Every control still needs its own accessible name. Icon-only buttons carry label; the toolbar's own label names the group and is required, because an unnamed toolbar is an unnamed region.

A composite child is one stop. This is why Radix was removed here: it moves focus only between primitives it registered, so a nested RadioGroup was invisible to it — two tab stops instead of one, and arrows that stepped over the radios. Toolbar is native now, and must not go back on the Radix list.

Keyboard

KeyResult
Arrow Left / Arrow RightMove between controls, clamping at the ends
Arrow Up / Arrow DownThe same, for a vertical toolbar
Home / EndJump to the first or last control
TabLeaves the toolbar — the whole set is one stop

Roles, states and properties

AttributeSet by
aria-labelset at render
aria-orientationset at render
rolealways toolbar, always separator

Focus

One tab stop for twelve buttons, and that is the entire point. Roving focus is computed rather than registered: the toolbar finds its items by looking for whatever is focusable inside it, so a composite child — a radio group, a menu button — is one item rather than invisible. Radix could not do that, which is why it was removed here.

Screen reader

Not recorded as verified. Stage 4 of the readiness ladder — driving each component with VoiceOver and NVDA — is unstarted across the package, and no component should claim otherwise until docs/journal/ records the session that did it.

Contrast and target size

npm run check:contrast audits every color pair this component uses across all four skins, light and dark, and fails the build on a shortfall. Two waivers are on the record in docs/audit/DEFERRED.md.

It takes the touch floor under (pointer: coarse) — the visual size is unchanged on a desktop pointer.

Tested

  • test/apg/batch-c.test.jsx
  • test/browser/upstream-toolbar-tree.spec.mjs

Known gaps

  • 1 upstream ref — their toolbar controls a textarea. A toolbar controlling one named region is their example, not the pattern.
  • 1 upstream ref — APG's own page or markup, not the pattern
  • 36 upstream refs — tests the toolbar's contents; each is another pattern with its own suite
  • 1 upstream ref — wrapping from the first control to the last. APG marks wrapping optional; ours clamps.

Appearance

TokenUsed for
--se-borderbackground, border
--se-border-widthblock-size, border, inline-size
--se-radius-mdborder-radius
--se-space-1gap, margin-block, margin-inline
--se-space-2gap, padding
--se-surfacebackground
--se-target-minmin-block-size

Motion

None of its own.

Content

The toolbar's label names the set — Formatting, Canvas tools.

Usage

When not to use it. For two or three unrelated actions. The cost of a toolbar is that a reader has to learn the arrow keys apply here; below about five controls that is a worse trade than five tab stops.

For joined actions that are each their own tab stop, use ButtonGroup.

Notes

ToolbarButton is a passthrough. The toolbar finds its focusable children itself, so wrapping a control is no longer what makes it reachable — it only says what the element is. Existing call sites keep working.

Forty-one of the toolbar's forty-six upstream refs test a menu button, a spinbutton and a radio group inside the toolbar. They are another pattern's contract, recorded as not applicable rather than ported twice.