forty-cdk
llms.txt

Primitives

Toolbar

A container that groups a set of controls under roving-tabindex navigation.

forty-cdk/toolbar WAI-ARIA APG

Move along the controls with the arrow keys. The toolbar keeps one tab stop, so Tab leaves it rather than walking every button in it.

The toolbar takes a single Tab stop and arrow keys move focus across its buttons, links, and toggle groups. Composes naturally with [forToggleGroup]: toggle items nested inside a toolbar register with the toolbar's roving tabindex automatically, so arrows move fluidly across the whole bar.

Anatomy

<div forToolbar aria-label="Formatting">
  <button forToolbarButton>Undo</button>
  <button forToolbarButton>Redo</button>
  <span forToolbarSeparator></span>
  <div forToggleGroup multiple>
    <button forToggleGroupItem value="bold">B</button>
    <button forToggleGroupItem value="italic">I</button>
  </div>
  <span forToolbarSeparator></span>
  <a forToolbarLink href="/help">Help</a>
</div>

Examples

import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { ForToggleGroup, ForToggleGroupItem } from 'forty-cdk/toggle';
import {
  ForToolbar,
  ForToolbarButton,
  ForToolbarLink,
  ForToolbarSeparator,
} from 'forty-cdk/toolbar';

@Component({
  selector: 'app-toolbar-default-example',
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [
    ForToolbar,
    ForToolbarButton,
    ForToolbarLink,
    ForToolbarSeparator,
    ForToggleGroup,
    ForToggleGroupItem,
  ],
  template: `
    <div forToolbar class="toolbar" aria-label="Text formatting">
      <button forToolbarButton class="toolbar-btn">Undo</button>
      <button forToolbarButton class="toolbar-btn">Redo</button>

      <span forToolbarSeparator class="toolbar-sep"></span>

      <div forToggleGroup class="toolbar-grp" multiple [(value)]="style" aria-label="Text style">
        <button forToggleGroupItem class="toolbar-btn toolbar-icon" value="bold" aria-label="Bold">
          B
        </button>
        <button
          forToggleGroupItem
          class="toolbar-btn toolbar-icon"
          value="italic"
          aria-label="Italic"
        >
          I
        </button>
        <button
          forToggleGroupItem
          class="toolbar-btn toolbar-icon"
          value="underline"
          aria-label="Underline"
        >
          U
        </button>
      </div>

      <span forToolbarSeparator class="toolbar-sep"></span>

      <div forToggleGroup class="toolbar-grp" [(value)]="align" aria-label="Alignment">
        <button
          forToggleGroupItem
          class="toolbar-btn toolbar-icon"
          value="left"
          aria-label="Align left"
        >
          L
        </button>
        <button
          forToggleGroupItem
          class="toolbar-btn toolbar-icon"
          value="center"
          aria-label="Align center"
        >
          C
        </button>
        <button
          forToggleGroupItem
          class="toolbar-btn toolbar-icon"
          value="right"
          aria-label="Align right"
        >
          R
        </button>
      </div>

      <span forToolbarSeparator class="toolbar-sep"></span>

      <a
        forToolbarLink
        class="toolbar-link"
        href="https://www.w3.org/WAI/ARIA/apg/patterns/toolbar/"
        target="_blank"
        rel="noreferrer noopener"
      >
        Docs
      </a>
    </div>
  `,
})
export class ToolbarDefaultExample {
  protected readonly style = signal<readonly string[]>(['bold']);
  protected readonly align = signal<readonly string[]>(['left']);
}

API

ForToolbar

Root directive. role="toolbar". Owns roving tabindex and arrow-key navigation.

PropertyTypeDescription
ariaLabel
input
Reactive accessible name, reflected as aria-label. Prefer aria-labelledby when a visible label element exists.
Default: null (and '') emits no attribute
orientation
input
Layout direction.
Default: 'horizontal'
dir
input
Reading direction. RTL swaps ArrowLeft / ArrowRight.
Default: —
loop
input
Whether arrow nav wraps at the ends.
Default: true
disabled
input
Disables every item.
Default: —
Data attributeValues
data-orientationhorizontal | vertical
data-disabledpresent | absent

ForToolbarButton

Plain push button. Apply on <button> so Enter / Space activate via native semantics.

PropertyTypeDescription
disabled
input
Per-item disabled, in addition to the toolbar's disabled. When true the button is announced as aria-disabled and clicks are suppressed. It stays focusable (no native disabled) so assistive tech still announces it.
Default: —
Data attributeValues
data-orientationhorizontal | vertical
data-disabledpresent | absent

Hyperlink. Apply on <a>; Enter follows the link via native semantics.

PropertyTypeDescription
disabled
input
Per-item disabled, in addition to the toolbar's disabled. When true the link is announced as aria-disabled and activation is suppressed. Because <a> has no native disabled, it stays focusable and assistive tech still announces it.
Default: —
Data attributeValues
data-orientationhorizontal | vertical
data-disabledpresent | absent

ForToolbarSeparator

Visual divider. Defaults orientation to the toolbar's cross-axis; reflects role="separator" + aria-orientation + data-orientation.

PropertyTypeDescription
orientation
input
Axis the separator divides along. Falls back to the toolbar's cross-axis when omitted.
Default: —
decorative
input
When true, gets role="none" and no aria-orientation.
Default: —
Data attributeValues
data-orientationhorizontal | vertical

Keyboard

KeyAction
ArrowRight / ArrowDownMove focus to the next enabled item (direction depends on orientation).
ArrowLeft / ArrowUpMove focus to the previous enabled item. RTL inverts the horizontal pair.
HomeMove focus to the first enabled item.
EndMove focus to the last enabled item.

Accessibility

Implements the WAI-ARIA Toolbar pattern.

  • Single Tab stop that follows focus. The toolbar takes one place in the tab order; only the entry-point item carries tabindex="0". Before any interaction the entry point is the first enabled item; once you move focus with the arrows (or Home / End), the tab stop follows the last focused item, so Shift+Tab back into the toolbar restores it (matching APG and the Tabs / Tree primitives). Arrow keys move focus inside, Home / End jump to the first / last enabled item.
  • Always label the toolbar. Pass the reactive [ariaLabel] input (or a native aria-labelledby pointing at a visible label element) so screen-reader users know what the toolbar acts on. Not optional: APG requires it.
  • Disabled items stay focusable. Both <button forToolbarButton> and <a forToolbarLink> expose aria-disabled="true" and suppress click rather than setting the native disabled attribute. Removing an item from the focus order would deviate from APG; users can still hear "disabled". An item disabled while it holds focus (an Undo button with nothing left to undo) keeps the arrow keys, Home and End, so the user can move on without leaving the toolbar.
  • Toggle groups don't change roles. Inside a toolbar, [forToggleGroup] keeps role="group" (semantically a related set of buttons). The toolbar role lives only on the outer container.
  • Cross-axis separators. [forToolbarSeparator] defaults to the orientation perpendicular to the toolbar so the line is visible. Override by setting orientation explicitly.

Styling

forty-cdk ships no styles: put your own class on each piece and key your CSS off the data-* attributes listed under API, not off the for* selectors (Styling forty-cdk explains why).

.toolbar {
  display: flex;
  gap: 0.25rem;
}

.toolbar[data-orientation='vertical'] {
  flex-direction: column;
}

.toolbar-btn[data-disabled],
.toolbar-link[data-disabled] {
  opacity: 0.4;
  pointer-events: none;
}

Wrapping in a design system

Subclass the root and re-provide FOR_TOOLBAR_CONTEXT with useExisting pointing at the subclass, since Angular does not inherit a directive's providers; Wrapping non-form roots walks the pattern.