Primitives
Button
Turns any element (a native <button> or a custom host like <div> / <span>) into an accessible button with keyboard activation. Disabled stays focusable (aria-disabled, never the native attribute) and pressed / hovered / focus-visible are reflected as data-* hooks.
Press and hold either control (a native <button> and a <span>) and watch data-pressed, data-hovered and data-focus-visible appear on both, so one rule styles the pair.
A single [forButton] directive does all of this. On a native <button> host the platform owns Enter/Space activation and type handling; on any non-button host the directive adds role="button", tabindex="0", and keyboard activation so the contract matches.
Anatomy
<!-- Native button — platform owns Enter/Space and type handling -->
<button forButton [disabled]="saving()" (activate)="save()">Save</button>
<!-- Non-button host — role="button", tabindex="0", keyboard activation added -->
<div forButton (activate)="save()">Save</div>
Examples
Disabled stays focusable
Per the APG, a disabled button must stay reachable so assistive tech can announce it. forButton never sets the native disabled attribute. Instead, it reflects aria-disabled='true' + data-disabled and makes activation a no-op. The native disabled button is skipped entirely.
Disabled
Disabled buttons stay focusable so assistive technology can announce them. The native disabled attribute is never set; instead aria-disabled="true" is reflected.
<button forButton [disabled]="isSaving()" (activate)="save()">Save</button>
A surrounding disabled [forFieldset] disables the button too: its disabled input is OR'd with the group's, so aria-disabled / data-disabled are reflected and activation is suppressed. This matters most on a non-native host (<div forButton>), which a native <fieldset disabled> cannot reach.
<fieldset forFieldset [disabled]="locked()">
<legend forFieldsetLegend>Account</legend>
<button forButton (activate)="save()">Save</button>
</fieldset>
Preserve consumer type
A native <button> without an explicit type attribute defaults to type="button". A consumer-set type="submit" is preserved:
<button type="submit" forButton>Submit form</button>
API
ForButton
| Property | Type | Description |
|---|---|---|
disabled | input | Suppresses activation and reflects aria-disabled + data-disabled. OR'd with a surrounding [forFieldset]'s disabled state.Default: false |
activate | output | Fires once per user activation (click, Enter, Space). Never fires when disabled. Default: — |
The directive reflects boolean data-* attributes (present with an empty-string value when true, absent when false). There is no data-state, because this primitive has no open/closed or checked/unchecked logical state.
| Data attribute | Values | |
|---|---|---|
data-disabled | present | absent | |
data-pressed | present | absent | |
data-hovered | present | absent | |
data-focus-visible | present | absent |
data-pressed is present while the primary pointer is held down or Enter/Space is held. data-hovered is present while a mouse/pen pointer is over the element. data-focus-visible is present when focused via keyboard (keyboard modality active).
Keyboard
On a native <button> host the platform owns activation: every key handler the directive binds returns immediately, and nothing in this table is its doing. On any other host (<div forButton>, <span forButton>) it synthesizes the activation itself, through the same (click) path a pointer takes. The split between the two keys is deliberately asymmetric, because that is what a native button does.
| Key | Action | |
|---|---|---|
Enter on a native <button> | The platform synthesizes the click; the directive adds nothing. | |
Enter on any other host | Activates on keydown, so (activate) fires while the key is still held. While disabled the event is left alone entirely: not even its default is prevented. | |
Space on a native <button> | The platform synthesizes the click on release and suppresses the page scroll itself. | |
Space on any other host | Activates on keyup, and only when the matching keydown reached the same host. Focus leaving mid-press drops the press. Its keydown always calls preventDefault() to stop the page scrolling, even while the button is disabled. |
Both keys drive data-pressed on every host: present from keydown until keyup, until focus leaves, or until the pointer is released. data-focus-visible instead follows the keyboard modality, so a keydown carrying Meta / Control / Alt is read as a shortcut and does not turn it on, while Shift does.
Accessibility
Implements the WAI-ARIA Button pattern.
- Native
<button>semantics are preserved. On a native host, no extra ARIA is added; the browser's built-in button role, keyboard activation, andtypehandling all apply. - Non-button hosts get
role="button"andtabindex="0", and the directive synthesizes the activation the platform would have (see Keyboard). - Disabled buttons stay focusable.
aria-disabled="true"is used instead of the nativedisabledattribute so assistive technology can still announce the control's purpose.
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).
[forButton][data-disabled] {
opacity: 0.4;
pointer-events: none;
}
[forButton][data-pressed] {
transform: scale(0.97);
}