Terracotta
GitHub

Most Terracotta composites navigate a specific child component. Toolbar navigates any focusable descendant, so you can drop buttons, links, inputs and <select> elements into it directly.

import { Toolbar } from 'terracotta/toolbar';

Anatomy

<Toolbar> {/* role="toolbar", one tab stop, arrow-key navigation */}
  {/* any focusable controls */}
</Toolbar>

Examples

Horizontal (default)

The orientation is published as aria-orientation. One CSS rule then keeps the layout and the semantics in step.

Vertical

<Toolbar class="toolbar" horizontal={false} aria-label="Tools">
  <button type="button" class="toolbar-button">Select</button>
  <button type="button" class="toolbar-button">Draw</button>
  <button type="button" class="toolbar-button">Erase</button>
</Toolbar>

With Terracotta controls

Toggle is a natural fit. Its tc-pressed attribute gives you the active state for free:

With separators and groups

The arrow keys skip non-focusable elements, so separators need no special handling:

<Toolbar class="toolbar" aria-label="Editor">
  <button type="button" class="toolbar-button">Undo</button>
  <button type="button" class="toolbar-button">Redo</button>
  <span class="toolbar-separator" role="separator" aria-orientation="vertical" />
  <button type="button" class="toolbar-button">Cut</button>
  <button type="button" class="toolbar-button">Copy</button>
</Toolbar>
.toolbar-separator {
  inline-size: 1px;
  align-self: stretch;
  margin-inline: 0.25rem;
  background: #d4d4d8;
}

Mixed control types

<Toolbar class="toolbar" aria-label="View">
  <button type="button" class="toolbar-button">Zoom in</button>
  <button type="button" class="toolbar-button">Zoom out</button>
  <select class="toolbar-select">
    <option>100%</option>
    <option>150%</option>
  </select>
  <a class="toolbar-button" href="/help">Help</a>
</Toolbar>

Disabled controls

A control disabled the native way, with disabled on a <button>, drops out of the focusable set on its own. Navigation skips a Terracotta control marked with tc-disabled for the same reason.

<Toolbar class="toolbar" aria-label="Editor">
  <button type="button" class="toolbar-button">Cut</button>
  <button type="button" class="toolbar-button" disabled>Paste</button>
</Toolbar>
.toolbar-button:disabled,
.toolbar-button[tc-disabled] {
  opacity: 0.4;
  cursor: not-allowed;
}

State attributes

Toolbar holds no state of its own. The controls inside it do.

Element Attribute Present when
Toolbar tc-toolbar Always

Orientation uses the standard aria-orientation attribute instead of a tc- one, because it is a genuine ARIA property.

Styling

[tc-toolbar] {
  display: flex;
  gap: 0.25rem;
}

[tc-toolbar][aria-orientation="vertical"] {
  flex-direction: column;
}

Reading the state in code

Toolbar exposes no state object. Read the state of the controls it contains instead, such as Toggle's pressed().

Keyboard

Key Action
→ / ← Next / previous control, when horizontal
↓ / ↑ Next / previous control, when vertical
Home / End First / last control
Tab / Shift+Tab Enters or leaves the toolbar, which is a single tab stop

Arrow navigation does not wrap around the ends.

The toolbar uses a roving tabindex:

  • The toolbar element itself is not focusable.
  • One control has tabindex="0". Every other control gets tabindex="-1".
  • The control with tabindex="0" is the one focused last, or the first enabled control if none has been focused yet.
  • The toolbar keeps this up to date when controls are added, removed or disabled.

Returning to a toolbar puts you back where you left off.

API

<Toolbar>

Renders a <div> by default.

Prop Type Default Description
as ValidConstructor 'div' Element or component to render as.
horizontal boolean true Orientation. Decides which arrow keys navigate and what aria-orientation reports.
ref DynamicNode<T> | (el) => void none Handle to the rendered element.
children JSX.Element none The controls. Not a render prop.
…rest props of as none Forwarded to the rendered element.

Give the toolbar an accessible name with aria-label or aria-labelledby. Both are forwarded like any other prop.

Rendered attributes

Attribute Value
role "toolbar"
tabindex 0
aria-orientation "horizontal" or "vertical"
tc-toolbar ""