Checkbox
Checkboxes let people select one or more items from a list, or turn an item on or off.
Usage
Checkbox selects one or more independent options. Associate every instance with a visible <label> using id / htmlFor in React, id / for in Angular, or id / for in Svelte.
Checked and disabled states
Use checked with onCheckedChange in React, or [(checked)] in Angular. defaultChecked captures the initial uncontrolled state. A controlled checkbox emits a request but does not change until its owner supplies the new value. Use disabled to prevent interaction and form changes.
import { useState } from 'react';
import { Checkbox } from '@udixio/ui-react';
export default function CheckboxStatesReact() {
const [checked, setChecked] = useState(false);
return (
<div className="grid gap-3 sm:grid-cols-2">
<div className="flex items-center gap-3">
<Checkbox
id="checkbox-react-controlled"
checked={checked}
onCheckedChange={setChecked}
/>
<label htmlFor="checkbox-react-controlled">Controlled</label>
</div>
<div className="flex items-center gap-3">
<Checkbox id="checkbox-react-default" defaultChecked />
<label htmlFor="checkbox-react-default">Default checked</label>
</div>
<div className="flex items-center gap-3">
<Checkbox id="checkbox-react-disabled" disabled />
<label htmlFor="checkbox-react-disabled">Disabled</label>
</div>
<div className="flex items-center gap-3">
<Checkbox
id="checkbox-react-disabled-checked"
defaultChecked
disabled
/>
<label htmlFor="checkbox-react-disabled-checked">
Disabled and checked
</label>
</div>
</div>
);
}import { ChangeDetectionStrategy, Component } from '@angular/core';
import { Checkbox } from '@udixio/ui-angular';
@Component({
selector: 'checkbox-states-angular-example',
standalone: true,
imports: [Checkbox],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<div class="grid gap-3 sm:grid-cols-2">
<div class="flex items-center gap-3">
<udx-checkbox id="checkbox-angular-controlled" [(checked)]="checked" />
<label for="checkbox-angular-controlled">Controlled</label>
</div>
<div class="flex items-center gap-3">
<udx-checkbox id="checkbox-angular-default" defaultChecked />
<label for="checkbox-angular-default">Default checked</label>
</div>
<div class="flex items-center gap-3">
<udx-checkbox id="checkbox-angular-disabled" disabled />
<label for="checkbox-angular-disabled">Disabled</label>
</div>
<div class="flex items-center gap-3">
<udx-checkbox
id="checkbox-angular-disabled-checked"
defaultChecked
disabled
/>
<label for="checkbox-angular-disabled-checked">
Disabled and checked
</label>
</div>
</div>
`,
})
export class CheckboxStatesAngular {
protected checked = false;
}<script lang="ts">
import { Checkbox } from '@udixio/ui-svelte';
let checked = $state(false);
</script>
<div class="grid gap-3 sm:grid-cols-2">
<div class="flex items-center gap-3">
<Checkbox id="checkbox-svelte-controlled" bind:checked />
<label for="checkbox-svelte-controlled">Controlled</label>
</div>
<div class="flex items-center gap-3">
<Checkbox id="checkbox-svelte-default" defaultChecked />
<label for="checkbox-svelte-default">Default checked</label>
</div>
<div class="flex items-center gap-3">
<Checkbox id="checkbox-svelte-disabled" disabled />
<label for="checkbox-svelte-disabled">Disabled</label>
</div>
<div class="flex items-center gap-3">
<Checkbox id="checkbox-svelte-disabled-checked" defaultChecked disabled />
<label for="checkbox-svelte-disabled-checked">Disabled and checked</label>
</div>
</div>Mixed selection
Use indeterminate for a parent option when its child selection is mixed. It does not change the underlying checked value. Keep the parent checked value derived from its children.
import { useState } from 'react';
import { Checkbox } from '@udixio/ui-react';
type Channel = 'email' | 'sms';
export default function CheckboxMixedSelectionReact() {
const [channels, setChannels] = useState<Record<Channel, boolean>>({
email: true,
sms: false,
});
const values = Object.values(channels);
const allChecked = values.every(Boolean);
const someChecked = values.some(Boolean);
const setAll = (checked: boolean) => {
setChannels({ email: checked, sms: checked });
};
const setChannel = (channel: Channel, checked: boolean) => {
setChannels((current) => ({ ...current, [channel]: checked }));
};
return (
<div className="flex flex-col gap-3">
<div className="flex items-center gap-3">
<Checkbox
id="checkbox-react-notifications"
checked={allChecked}
indeterminate={someChecked && !allChecked}
onCheckedChange={setAll}
/>
<label htmlFor="checkbox-react-notifications">Notifications</label>
</div>
<div className="ml-6 flex flex-col gap-3">
<div className="flex items-center gap-3">
<Checkbox
id="checkbox-react-email"
checked={channels.email}
onCheckedChange={(checked) => setChannel('email', checked)}
/>
<label htmlFor="checkbox-react-email">Email</label>
</div>
<div className="flex items-center gap-3">
<Checkbox
id="checkbox-react-sms"
checked={channels.sms}
onCheckedChange={(checked) => setChannel('sms', checked)}
/>
<label htmlFor="checkbox-react-sms">SMS</label>
</div>
</div>
</div>
);
}import { ChangeDetectionStrategy, Component } from '@angular/core';
import { Checkbox } from '@udixio/ui-angular';
type Channel = 'email' | 'sms';
@Component({
selector: 'checkbox-mixed-selection-angular-example',
standalone: true,
imports: [Checkbox],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<div class="flex flex-col gap-3">
<div class="flex items-center gap-3">
<udx-checkbox
id="checkbox-angular-notifications"
[checked]="allChecked"
[indeterminate]="someChecked && !allChecked"
(checkedChange)="setAll($event)"
/>
<label for="checkbox-angular-notifications">Notifications</label>
</div>
<div class="ml-6 flex flex-col gap-3">
<div class="flex items-center gap-3">
<udx-checkbox
id="checkbox-angular-email"
[checked]="channels.email"
(checkedChange)="setChannel('email', $event)"
/>
<label for="checkbox-angular-email">Email</label>
</div>
<div class="flex items-center gap-3">
<udx-checkbox
id="checkbox-angular-sms"
[checked]="channels.sms"
(checkedChange)="setChannel('sms', $event)"
/>
<label for="checkbox-angular-sms">SMS</label>
</div>
</div>
</div>
`,
})
export class CheckboxMixedSelectionAngular {
protected channels: Record<Channel, boolean> = {
email: true,
sms: false,
};
protected get allChecked(): boolean {
return Object.values(this.channels).every(Boolean);
}
protected get someChecked(): boolean {
return Object.values(this.channels).some(Boolean);
}
protected setAll(checked: boolean): void {
this.channels = { email: checked, sms: checked };
}
protected setChannel(channel: Channel, checked: boolean): void {
this.channels = { ...this.channels, [channel]: checked };
}
}<script lang="ts">
import { Checkbox } from '@udixio/ui-svelte';
type Channel = 'email' | 'sms';
let channels = $state<Record<Channel, boolean>>({ email: true, sms: false });
const values = $derived(Object.values(channels));
const allChecked = $derived(values.every(Boolean));
const someChecked = $derived(values.some(Boolean));
const setAll = (checked: boolean) => {
channels = { email: checked, sms: checked };
};
const setChannel = (channel: Channel, checked: boolean) => {
channels = { ...channels, [channel]: checked };
};
</script>
<div class="flex flex-col gap-3">
<div class="flex items-center gap-3">
<Checkbox
id="checkbox-svelte-notifications"
bind:checked={() => allChecked, setAll}
indeterminate={someChecked && !allChecked}
/>
<label for="checkbox-svelte-notifications">Notifications</label>
</div>
<div class="ml-6 flex flex-col gap-3">
<div class="flex items-center gap-3">
<Checkbox
id="checkbox-svelte-email"
bind:checked={() => channels.email, (checked) => setChannel('email', checked)}
/>
<label for="checkbox-svelte-email">Email</label>
</div>
<div class="flex items-center gap-3">
<Checkbox
id="checkbox-svelte-sms"
bind:checked={() => channels.sms, (checked) => setChannel('sms', checked)}
/>
<label for="checkbox-svelte-sms">SMS</label>
</div>
</div>
</div>Form validation
Set name and value when the checkbox participates in form submission. Use required for a mandatory choice. Expose the error state with invalid and connect its explanation with aria-describedby.
import { useState, type FormEvent } from 'react';
import { Button, Checkbox } from '@udixio/ui-react';
export default function CheckboxValidationReact() {
const [accepted, setAccepted] = useState(false);
const [submitted, setSubmitted] = useState(false);
const invalid = submitted && !accepted;
const submit = (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
setSubmitted(true);
};
return (
<form
className="flex flex-col items-start gap-3"
noValidate
onSubmit={submit}
>
<div className="flex items-center gap-3">
<Checkbox
id="checkbox-react-terms"
name="terms"
value="accepted"
checked={accepted}
required
invalid={invalid}
aria-describedby="checkbox-react-terms-description"
onCheckedChange={setAccepted}
/>
<label htmlFor="checkbox-react-terms">Accept the terms</label>
</div>
<p
id="checkbox-react-terms-description"
className={invalid ? 'text-error' : 'text-on-surface-variant'}
aria-live="polite"
>
{invalid ? 'You must accept the terms.' : 'Required to continue.'}
</p>
<Button type="submit" label="Continue" />
</form>
);
}import { ChangeDetectionStrategy, Component } from '@angular/core';
import { Button, Checkbox } from '@udixio/ui-angular';
@Component({
selector: 'checkbox-validation-angular-example',
standalone: true,
imports: [Button, Checkbox],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<form
class="flex flex-col items-start gap-3"
novalidate
(submit)="submit($event)"
>
<div class="flex items-center gap-3">
<udx-checkbox
id="checkbox-angular-terms"
name="terms"
value="accepted"
[checked]="accepted"
required
[invalid]="invalid"
aria-describedby="checkbox-angular-terms-description"
(checkedChange)="accepted = $event"
/>
<label for="checkbox-angular-terms">Accept the terms</label>
</div>
<p
id="checkbox-angular-terms-description"
[class]="invalid ? 'text-error' : 'text-on-surface-variant'"
aria-live="polite"
>
{{ invalid ? 'You must accept the terms.' : 'Required to continue.' }}
</p>
<udx-button type="submit" label="Continue" />
</form>
`,
})
export class CheckboxValidationAngular {
protected accepted = false;
protected submitted = false;
protected get invalid(): boolean {
return this.submitted && !this.accepted;
}
protected submit(event: SubmitEvent): void {
event.preventDefault();
this.submitted = true;
}
}<script lang="ts">
import { Button, Checkbox } from '@udixio/ui-svelte';
let accepted = $state(false);
let submitted = $state(false);
const invalid = $derived(submitted && !accepted);
const submit = (event: SubmitEvent) => {
event.preventDefault();
submitted = true;
};
</script>
<form class="flex flex-col items-start gap-3" novalidate onsubmit={submit}>
<div class="flex items-center gap-3">
<Checkbox
id="checkbox-svelte-terms"
name="terms"
value="accepted"
bind:checked={accepted}
required
{invalid}
aria-describedby="checkbox-svelte-terms-description"
/>
<label for="checkbox-svelte-terms">Accept the terms</label>
</div>
<p
id="checkbox-svelte-terms-description"
class={invalid ? 'text-error' : 'text-on-surface-variant'}
aria-live="polite"
>
{invalid ? 'You must accept the terms.' : 'Required to continue.'}
</p>
<Button type="submit" label="Continue" />
</form>Accessibility
Checkbox renders a native checkbox, so Space toggles it and assistive technologies receive checked, mixed, disabled, and invalid states directly. The 40px touch target exposes a visible focus outline. This component never creates a label or an error message on its own.