Primitives
Toolbar
A container that groups a set of controls under roving-tabindex navigation.
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.
| Property | Type | Description |
|---|---|---|
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 attribute | Values | |
|---|---|---|
data-orientation | horizontal | vertical | |
data-disabled | present | absent |
ForToolbarButton
Plain push button. Apply on <button> so Enter / Space activate via native semantics.
| Property | Type | Description |
|---|---|---|
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 attribute | Values | |
|---|---|---|
data-orientation | horizontal | vertical | |
data-disabled | present | absent |
ForToolbarLink
Hyperlink. Apply on <a>; Enter follows the link via native semantics.
| Property | Type | Description |
|---|---|---|
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 attribute | Values | |
|---|---|---|
data-orientation | horizontal | vertical | |
data-disabled | present | absent |
ForToolbarSeparator
Visual divider. Defaults orientation to the toolbar's cross-axis; reflects role="separator" + aria-orientation + data-orientation.
| Property | Type | Description |
|---|---|---|
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 attribute | Values | |
|---|---|---|
data-orientation | horizontal | vertical |
Keyboard
| Key | Action | |
|---|---|---|
ArrowRight / ArrowDown | Move focus to the next enabled item (direction depends on orientation). | |
ArrowLeft / ArrowUp | Move focus to the previous enabled item. RTL inverts the horizontal pair. | |
Home | Move focus to the first enabled item. | |
End | Move 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 nativearia-labelledbypointing 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>exposearia-disabled="true"and suppress click rather than setting the nativedisabledattribute. 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]keepsrole="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 settingorientationexplicitly.
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.