Primitives
Checkbox
A checkbox supporting the three states checked, unchecked and indeterminate.
Tick it with the pointer or Space and watch data-state move between checked and unchecked. The box is a <button>, so its whole appearance is your CSS.
Headless and styleless. Implements FormCheckboxControl from @angular/forms/signals for [formField] auto-wiring.
When to choose
- Checkbox: deferred selection (user is choosing options for a form to apply later). Supports the tri-state
mixedvalue, useful for "select all" parents. - Switch: immediate setting (flipping it changes the world right now). Always binary.
Use the one that matches your semantics. ForCheckbox and ForSwitch are intentionally separate even though they share most of their state surface.
Anatomy
<label>
<button forCheckbox [(checked)]="agreed">
<span forCheckboxIndicator></span>
</button>
I agree to the terms
</label>
Examples
Tri-state ("select all")
A parent checkbox reflects indeterminate when only some children are selected. Activating it selects or clears them all at once, matching native inputs.
States
One class and one directive, three states. disabled and readonly both keep the box focusable and announced (per APG) while click and Space are a no-op; they reflect data-disabled and data-readonly, which is all the example's stylesheet keys on.
Signal Forms
forCheckbox implements FormCheckboxControl, so a single [formField] binding wires the binary checked value into the form and pulls validity back out. The box is required: blur it unchecked and it reflects data-invalid / data-touched.
API
ForCheckbox
| Property | Type | Description |
|---|---|---|
checked | model | Two-way bindable on/off. Required by FormCheckboxControl.Default: — |
indeterminate | model | Two-way bindable. When true, aria-checked="mixed" regardless of checked. Click clears it. UI-only and not part of the form value.Default: — |
disabled / readonly / required / invalid / pending | input | Reflected as the matching aria-* / data-* attributes. A disabled checkbox stays focusable (per APG), with aria-disabled="true" + data-disabled and no native disabled; click is a no-op.Default: — |
name | input | Reflected on name (empty string omits the attribute).Default: '' |
errors | input | Validation errors fed by [formField].Default: — |
touched | model | Set to true on blur.Default: — |
| Data attribute | Values | |
|---|---|---|
data-state | checked | unchecked | indeterminate | |
data-disabled | present | absent | |
data-readonly | present | absent |
ForCheckboxIndicator
Optional styling slot inside a [forCheckbox]. Mirrors the parent's data-state so you can show or hide a check / dash without per-state bindings.
| Data attribute | Values | |
|---|---|---|
data-state | checked | unchecked | indeterminate |
Keyboard
| Key | Action | |
|---|---|---|
Space | Toggle the checkbox. The only key APG mandates. | |
Enter | Also toggles. A documented superset, not an APG violation. |
Activating an indeterminate checkbox clears indeterminate and toggles checked (matches native <input type="checkbox">).
Both keys work on any host element. On a <button> they come from native button behavior; on any other host (<div>, <span>, or a hostDirectives wrapper's own host) the directive adds tabindex="0" and synthesizes the same activation, so a styled-from-scratch checkbox is never announced as a checkbox it is impossible to operate. Space keydown always blocks page scrolling; the toggle fires on its keyup.
Accessibility
Implements the WAI-ARIA Checkbox pattern.
- Provide an accessible name. Wrap the button in a
<label>, or setaria-labelledby/aria-label. Without one, the control is announced as just "checkbox" with no purpose. - Any host element works. A
<button>is the recommended host (the directive forcestype="button"through a host binding, so it never submits a surrounding form even if you writetype="submit"yourself), but a non-button host getstabindex="0"and synthesizedSpace/Enteractivation, so it is keyboard-operable too. A non-button host gets notypeattribute at all, becausetypeis not valid on a<div>/<span>and there is no form submission to protect against. role="checkbox"witharia-checked="mixed"is the canonical tri-state contract. Some legacy screen readers handle "mixed" differently, so test with your target SRs.
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).
.cb-check {
display: inline-block;
width: 6px;
height: 12px;
border: solid currentColor;
border-width: 0 2px 2px 0;
transform: rotate(45deg);
}
.cb-row[data-state='unchecked'] .cb-check {
display: none;
}
.cb-row[data-state='indeterminate'] .cb-check {
width: 12px;
height: 0;
transform: none;
}
Wrapping in a design system
Wrapping form primitives documents both supported wrapper patterns: hostDirectives with the exported FOR_CHECKBOX_HOST_DIRECTIVE_INPUTS / FOR_CHECKBOX_HOST_DIRECTIVE_OUTPUTS name tuples, and subclassing.