Form
Checkbox
Usage#
import { WdsCheckboxComponent } from '@wds/angular/checkbox';Examples#
Default#
import { Component } from '@angular/core';
import { WdsCheckboxComponent } from '@wds/angular/checkbox';
@Component({
selector: 'app-checkbox-default-demo',
imports: [WdsCheckboxComponent],
template: `
<wds-checkbox>Accept terms and conditions</wds-checkbox>
`,
})
export class CheckboxDefaultDemo {}Description#
description slot adds helper text below the label.
import { Component } from '@angular/core';
import { WdsCheckboxComponent } from '@wds/angular/checkbox';
@Component({
selector: 'app-checkbox-description-demo',
imports: [WdsCheckboxComponent],
template: `
<div class="flex w-full max-w-sm flex-col">
<wds-checkbox checked>
Accept terms and conditions
<span slot="description">By clicking this checkbox, you agree to the terms and conditions.</span>
</wds-checkbox>
</div>
`,
})
export class CheckboxDescriptionDemo {}Disabled#
import { Component } from '@angular/core';
import { WdsCheckboxComponent } from '@wds/angular/checkbox';
@Component({
selector: 'app-checkbox-disabled-demo',
imports: [WdsCheckboxComponent],
template: `
<div class="flex flex-col gap-3">
<wds-checkbox disabled>Enable notifications</wds-checkbox>
<wds-checkbox disabled checked>Remember me</wds-checkbox>
</div>
`,
})
export class CheckboxDisabledDemo {}Indeterminate#
indeterminate shows a dash, e.g. for "select all" when only some items are selected. A click clears it.
import { Component } from '@angular/core';
import { WdsCheckboxComponent } from '@wds/angular/checkbox';
@Component({
selector: 'app-checkbox-indeterminate-demo',
imports: [WdsCheckboxComponent],
template: `
<wds-checkbox indeterminate>Select all</wds-checkbox>
`,
})
export class CheckboxIndeterminateDemo {}Group#
wds-field-set with a legend groups related checkboxes; data-checkbox-group on wds-field-group tightens the spacing. wds-change bubbles, so one listener on the group handles all checkboxes.
{ "hardDisks": true, "externalDisks": true, "media": false, "servers": false }
import { JsonPipe } from '@angular/common';
import { Component, signal } from '@angular/core';
import { FormField, form } from '@angular/forms/signals';
import { WdsCheckboxComponent } from '@wds/angular/checkbox';
import { WDS_FIELD } from '@wds/angular/field';
import { WdsCheckedField } from '@wds/angular/forms';
@Component({
selector: 'app-checkbox-group-demo',
imports: [...WDS_FIELD, FormField, JsonPipe, WdsCheckboxComponent, WdsCheckedField],
template: `
<div class="flex w-sm max-w-full flex-col gap-3">
<wds-field-set>
<wds-field-legend variant="label">Show these items on the desktop:</wds-field-legend>
<wds-field-description>Select the items you want to show on the desktop.</wds-field-description>
<wds-field-group data-checkbox-group>
<wds-checkbox [formField]="desktopForm.hardDisks">Hard disks</wds-checkbox>
<wds-checkbox [formField]="desktopForm.externalDisks">External disks</wds-checkbox>
<wds-checkbox [formField]="desktopForm.media">CDs, DVDs, and iPods</wds-checkbox>
<wds-checkbox [formField]="desktopForm.servers">Connected servers</wds-checkbox>
</wds-field-group>
</wds-field-set>
<p class="m-0 font-mono text-sm text-muted-foreground">{{ desktop() | json }}</p>
</div>
`,
})
export class CheckboxGroupDemo {
protected readonly desktop = signal({ hardDisks: true, externalDisks: true, media: false, servers: false });
protected readonly desktopForm = form(this.desktop);
}Select all#
indeterminate when only some are. A click on it checks or unchecks the whole group. indeterminate is state for the app to set; the checkbox only clears it on a click.
import { Component, computed, signal } from '@angular/core';
import { FormField, form } from '@angular/forms/signals';
import { WdsCheckboxComponent } from '@wds/angular/checkbox';
import { WDS_FIELD } from '@wds/angular/field';
import { WdsCheckedField } from '@wds/angular/forms';
type Toppings = Record<'cheese' | 'mushrooms' | 'olives', boolean>;
@Component({
selector: 'app-checkbox-select-all-demo',
imports: [...WDS_FIELD, FormField, WdsCheckboxComponent, WdsCheckedField],
template: `
<wds-field-set class="w-sm max-w-full">
<wds-field-legend variant="label">Toppings</wds-field-legend>
<wds-checkbox [checked]="all()" [indeterminate]="some()" (wdsChange)="setAll($event.checked)">Select all</wds-checkbox>
<wds-field-group data-checkbox-group class="ps-6">
<wds-checkbox [formField]="toppingsForm.cheese">Cheese</wds-checkbox>
<wds-checkbox [formField]="toppingsForm.mushrooms">Mushrooms</wds-checkbox>
<wds-checkbox [formField]="toppingsForm.olives">Olives</wds-checkbox>
</wds-field-group>
</wds-field-set>
`,
})
export class CheckboxSelectAllDemo {
protected readonly toppings = signal<Toppings>({ cheese: true, mushrooms: false, olives: false });
protected readonly toppingsForm = form(this.toppings);
private readonly values = computed(() => Object.values(this.toppings()));
protected readonly all = computed(() => this.values().every(Boolean));
/** Some, but not all, toppings are selected. */
protected readonly some = computed(() => this.values().some(Boolean) && !this.all());
protected setAll(checked: boolean): void {
this.toppings.set({ cheese: checked, mushrooms: checked, olives: checked });
}
}Choice card#
variant="card" turns the checkbox into a card: a click anywhere on it toggles the checkbox.
import { Component } from '@angular/core';
import { WdsCheckboxComponent } from '@wds/angular/checkbox';
@Component({
selector: 'app-checkbox-choice-card-demo',
imports: [WdsCheckboxComponent],
template: `
<div class="flex w-full max-w-sm flex-col gap-3">
<wds-checkbox variant="card" checked>
Analytics
<span slot="description">Usage reports and statistics.</span>
</wds-checkbox>
<wds-checkbox variant="card">
Backups
<span slot="description">Daily copies of your data.</span>
</wds-checkbox>
<wds-checkbox variant="card" checked>
Priority support
<span slot="description">A reply within 24 hours.</span>
</wds-checkbox>
</div>
`,
})
export class CheckboxChoiceCardDemo {}Invalid#
invalid shows the error state and sets aria-invalid.
import { Component } from '@angular/core';
import { WdsCheckboxComponent } from '@wds/angular/checkbox';
@Component({
selector: 'app-checkbox-invalid-demo',
imports: [WdsCheckboxComponent],
template: `
<wds-checkbox invalid>Accept terms and conditions</wds-checkbox>
`,
})
export class CheckboxInvalidDemo {}Forms#
Integration with Angular forms. Pick the API your app uses.
Signal forms#
WdsCheckboxField connects the checkbox to [formField]. For a boolean, required() means checked: false counts as empty.
import { Component, computed, signal } from '@angular/core';
import { FormField, FormRoot, form, required } from '@angular/forms/signals';
import { WdsButtonComponent } from '@wds/angular/button';
import { WdsCheckboxComponent } from '@wds/angular/checkbox';
import { WDS_FIELD } from '@wds/angular/field';
import { WdsCheckedField } from '@wds/angular/forms';
@Component({
selector: 'app-checkbox-signal-forms-demo',
imports: [...WDS_FIELD, FormField, FormRoot, WdsButtonComponent, WdsCheckboxComponent, WdsCheckedField],
template: `
<form class="flex w-sm max-w-full flex-col gap-4" [formRoot]="signupForm">
<wds-field [invalid]="showTermsError()">
<wds-checkbox [formField]="signupForm.terms">Accept terms and conditions</wds-checkbox>
<wds-field-error [errors]="showTermsError() ? signupForm.terms().errors() : []" />
</wds-field>
<wds-checkbox [formField]="signupForm.updates">
Send me product updates
<span slot="description">At most one email a month.</span>
</wds-checkbox>
<wds-button type="submit" class="self-start">Submit</wds-button>
<p class="m-0 font-mono text-sm text-muted-foreground">Submitted: {{ submitted() }}</p>
</form>
`,
})
export class CheckboxSignalFormsDemo {
protected readonly signup = signal({ terms: false, updates: false });
protected readonly submitted = signal('—');
protected readonly signupForm = form(
this.signup,
(path) => {
// For a boolean `required` means checked: `false` counts as empty.
required(path.terms, { message: 'You must accept the terms and conditions.' });
},
{
submission: {
action: async (form) => {
this.submitted.set(JSON.stringify(form().value()));
},
},
}
);
/** The error appears after the user leaves the checkbox or submits the form. */
protected readonly showTermsError = computed(() => this.signupForm.terms().touched() && this.signupForm.terms().invalid());
}Template-driven#
WdsCheckboxValueAccessor makes ngModel work on wds-checkbox. Add WdsCheckboxRequiredValidator so that required means checked, as on a native checkbox; without it false is a valid value.
import { Component, signal } from '@angular/core';
import { FormsModule, type NgForm } from '@angular/forms';
import { WdsButtonComponent } from '@wds/angular/button';
import { WdsCheckboxComponent } from '@wds/angular/checkbox';
import { WDS_FIELD } from '@wds/angular/field';
import { WdsCheckboxRequiredValidator, WdsCheckedValueAccessor } from '@wds/angular/forms';
@Component({
selector: 'app-checkbox-template-driven-demo',
imports: [...WDS_FIELD, FormsModule, WdsButtonComponent, WdsCheckboxComponent, WdsCheckboxRequiredValidator, WdsCheckedValueAccessor],
template: `
<form #signupForm="ngForm" class="flex w-sm max-w-full flex-col gap-4" (ngSubmit)="save(signupForm)">
<wds-field [invalid]="!!terms.invalid && !!terms.touched">
<wds-checkbox #terms="ngModel" name="terms" [(ngModel)]="signup.terms" required>Accept terms and conditions</wds-checkbox>
@if (terms.invalid && terms.touched) {
<wds-field-error>You must accept the terms and conditions.</wds-field-error>
}
</wds-field>
<wds-checkbox name="updates" [(ngModel)]="signup.updates">
Send me product updates
<span slot="description">At most one email a month.</span>
</wds-checkbox>
<wds-button type="submit" class="self-start">Submit</wds-button>
<p class="m-0 font-mono text-sm text-muted-foreground">Submitted: {{ submitted() }}</p>
</form>
`,
})
export class CheckboxTemplateDrivenDemo {
protected readonly signup = { terms: false, updates: false };
protected readonly submitted = signal('—');
protected save(form: NgForm): void {
if (form.invalid) {
form.form.markAllAsTouched();
return;
}
this.submitted.set(JSON.stringify(form.value));
}
}Reactive forms#
WdsCheckboxValueAccessor works with formControl and formControlName. Use Validators.requiredTrue for a checkbox that must be checked.
import { Component, signal } from '@angular/core';
import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
import { WdsButtonComponent } from '@wds/angular/button';
import { WdsCheckboxComponent } from '@wds/angular/checkbox';
import { WDS_FIELD } from '@wds/angular/field';
import { WdsCheckedValueAccessor } from '@wds/angular/forms';
@Component({
selector: 'app-checkbox-reactive-forms-demo',
imports: [...WDS_FIELD, ReactiveFormsModule, WdsButtonComponent, WdsCheckboxComponent, WdsCheckedValueAccessor],
template: `
<form class="flex w-sm max-w-full flex-col gap-4" [formGroup]="signupForm" (ngSubmit)="save()">
<wds-field [invalid]="terms.invalid && terms.touched">
<wds-checkbox formControlName="terms">Accept terms and conditions</wds-checkbox>
@if (terms.invalid && terms.touched) {
<wds-field-error>You must accept the terms and conditions.</wds-field-error>
}
</wds-field>
<wds-checkbox formControlName="updates">
Send me product updates
<span slot="description">At most one email a month.</span>
</wds-checkbox>
<wds-button type="submit" class="self-start">Submit</wds-button>
<p class="m-0 font-mono text-sm text-muted-foreground">Submitted: {{ submitted() }}</p>
</form>
`,
})
export class CheckboxReactiveFormsDemo {
protected readonly signupForm = new FormGroup({
// `requiredTrue`, not `required`: for `required` the value `false` is valid.
terms: new FormControl(false, { nonNullable: true, validators: [Validators.requiredTrue] }),
updates: new FormControl(false, { nonNullable: true }),
});
protected readonly terms = this.signupForm.controls.terms;
protected readonly submitted = signal('—');
protected save(): void {
if (this.signupForm.invalid) {
this.signupForm.markAllAsTouched();
return;
}
this.submitted.set(JSON.stringify(this.signupForm.getRawValue()));
}
}API References#
wds-checkbox#
<input type="checkbox"> inside (keyboard, accessibility, click on the label).
Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
| indeterminate | boolean | false | aria-checked="mixed". A click clears it, as in a native checkbox. |
| checked | boolean | false | wds-change. |
| disabled | boolean | false | |
| invalid | boolean | false | aria-invalid. |
| required | boolean | false | |
| name | string | '' | |
| value | string | 'on' | |
| variant | WdsToggleVariant | 'default' | card is a clickable card with a border and the control on the right. |
Events
| Event | Description |
|---|---|
| wds-changeCustomEvent<WdsToggleChangeDetail> |
Methods
| Method | Description |
|---|---|
| formResetCallback() | |
| formDisabledCallback(disabled: boolean) | |
| checkValidity() | true when the value is valid; otherwise fires invalid on the element. |
| reportValidity() | checkValidity(), and shows the browser validation bubble when the value is invalid. |
| setCustomValidity(message: string) | input.setCustomValidity(). |
Slots
| Slot | Description |
|---|---|
| (default) | |
| description | aria-describedby). |
CSS parts (::part)
| Part | Description |
|---|---|
| root | card variant). |
| control | <input>. |
| icon | |
| label | |
| description |
CSS custom properties
| Property | Description |
|---|---|
| --wds-checkbox-border | |
| --wds-checkbox-checked |
Accessibility#
Screen reader#
wds-checkbox renders a native <input type="checkbox">, so screen readers announce it as a checkbox with its state. indeterminate is announced as mixed (aria-checked="mixed").
<label> of the input and the description slot is linked with aria-describedby. Without visible text give the checkbox an aria-label: it is forwarded to the input.
invalid sets aria-invalid="true" and required sets aria-required="true"; disabled disables the native input. In wds-field-set the legend names the group.
value (default on) is submitted under name.
Keyboard support#
| Key | Function |
|---|---|