Checkbox
Choose independent options with a stable label, real state and a meaningful mixed aggregate when needed.
Choose an independent option
Use Checkbox when multiple independent choices may coexist or one explicit option is included/excluded. Use RadioGroup for one alternative and Switch for a binary feature setting. Do not use a checkbox to imply consent to a consequence that its label does not describe.
A mixed checkbox summarizes partial selection; it is not a third independent value for an ordinary item. CheckboxGroup owns the parent aggregate in the grouped example.
Anatomy and specifications
| Part | Actual implementation |
|---|---|
| Label target | Native label wrapping the primitive and visible text; min 44px height/width |
| Control | Default span with checkbox semantics and associated hidden input; public HTMLElement ref |
| Visual box | 20×20px, 2px readable boundary, --radius-sm |
| Checked / mixed | Primary fill; decorative check mark / horizontal dash |
| Name | label string supplies visible text and default aria-label; native ARIA props can override |
| Focus / unavailable | Token ring; disabled cursor/visual state; read-only remains distinct |
| Transition | Token duration; removed under reduced motion |
Material choice, targets and source caveat
Use a checkbox set for related choices that can coexist, a radio group for one alternative, and a switch for a standalone on/off setting. The reference allows an immediately effective desktop checkbox setting; a form checkbox can instead remain draft until submission. State which policy the task uses. Related controls can move into a side sheet on a large screen without changing their labels, group question or selection model.
The captured checkbox accessibility keyboard table refers to chips, arrow movement and Delete/Backspace removal. That table is inconsistent with the checkbox topic; it is not copied as this control’s keyboard contract. The installed checkbox supports ordinary Tab focus and Space toggling, with native label activation as verified by the local examples and tests.
| Reference point | Local implementation |
|---|---|
| Anatomy and measurements | Native reference: 18dp box/icon, 2dp corner, 40dp state layer and 48dp target. Local box is nominally 20 CSS px at a 16px root within a minimum 44px labelled target. These are independent contracts, not a dp conversion. |
| Selection and error | Check, dash and unchecked outline distinguish selected/mixed/empty. Required errors retain choices, use aria-invalid and a linked persistent explanation. The package does not claim separate Material error tokens for each selected/mixed state. |
| Parent aggregate | None/some/all follows actual group members. Selecting mixed chooses all; selecting all clears. The named parent is not an extra submitted member. Read the Checkbox Group guide for changing, unavailable or filtered membership. |
| Label and density | Adjacent text stays the foreground color when checked; click text or control to operate. Material recommends 48×48 CSS px targets and opt-in reversible density. This site uses a 44px baseline and provides no default dense mode or automatic density preference. |
| Interaction style | The local ring, boundary and immediate role-color change distinguish interaction; there is no native state-layer/ripple or an asserted hardware feedback implementation. |
Working required local option and form
No local form has been submitted.
'use client';
import { useRef, useState } from 'react';
import { Button, Checkbox, CheckboxGroup } from '@mobtranslate/ui';
export function CheckboxSelectionExample() {
const [checked, setChecked] = useState(false);
const [error, setError] = useState('');
const [result, setResult] = useState('No local form has been submitted.');
const control = useRef<HTMLElement>(null);
return <div data-checkbox-lab className="space-y-4">
<form noValidate className="space-y-3" onSubmit={event => {
event.preventDefault();
if (!checked) { setError('Include explanatory notes before submitting this local sample.'); control.current?.focus(); return; }
setError(''); setResult(`Local FormData: notes=${new FormData(event.currentTarget).get('notes')}. No request was sent.`);
}}>
<Checkbox ref={control} label="Include explanatory notes" name="notes" value="include" uncheckedValue="omit" required checked={checked} onCheckedChange={next => { setChecked(next); setError(''); }} aria-invalid={!!error || undefined} aria-describedby={error ? 'local-checkbox-error' : undefined} className={state => state.checked ? 'local-checkbox-checked' : 'local-checkbox-unchecked'} />
{error && <p id="local-checkbox-error" role="alert">{error}</p>}
<Button type="submit">Submit local checkbox sample</Button>
</form>
<Checkbox label="Read-only checked sample" checked readOnly />
<Checkbox label="Disabled unchecked sample" disabled />
<p role="status">{result}</p>
</div>;
}
export function CheckboxGroupSelectionExample() {
const options = ['text','audio','images'];
const [selected, setSelected] = useState<string[]>(['text']);
const [disabled, setDisabled] = useState(false);
const [result, setResult] = useState('No local grouped form submitted.');
return <div data-checkbox-group-lab className="space-y-4">
<Button type="button" variant="outline" aria-pressed={disabled} onClick={() => setDisabled(current => !current)}>Disable local checkbox group</Button>
<form className="space-y-3" onSubmit={event => { event.preventDefault(); setResult(`Local FormData resources: ${new FormData(event.currentTarget).getAll('resource').join(', ') || 'none'}.`); }}>
<fieldset><legend className="font-semibold">Local resource format filters</legend><p>Selection describes this sample, not permission to use any recording or image.</p>
<CheckboxGroup value={selected} allValues={options} onValueChange={setSelected} disabled={disabled} aria-label="Local resource format filters" className={state => state.disabled ? 'local-group-disabled' : 'local-group-enabled'}>
<Checkbox parent label="Select all local formats" />
{options.map(value => <Checkbox key={value} value={value} name="resource" label={`Include ${value} samples`} />)}
</CheckboxGroup>
</fieldset>
<Button type="submit">Submit grouped sample</Button>
</form>
<p role="status">Selected local formats: {selected.join(', ') || 'none'}. {result}</p>
</div>;
}
API reference
| Prop | Contract |
|---|---|
| label | Optional visible string and default accessible name; provide a meaningful name when omitted. |
| checked / defaultChecked | Controlled boolean / initial uncontrolled boolean; default unchecked. |
| onCheckedChange | (boolean, eventDetails); details.cancel() can reject the change. Use this rather than native onChange. |
| disabled / readOnly / required | false defaults; unavailable, immutable and required are different states. |
| name / uncheckedValue | Form field name; unchecked is omitted unless an explicit uncheckedValue is supplied. |
| inputRef | Ref to hidden HTMLInputElement. The public control ref is HTMLElement, a span by default. |
| aria-label / aria-labelledby / aria-describedby / aria-invalid | Connect a stable name, concise explanation and current error. |
| className / style / render / native props | Installed primitive props; styled wrappers retain string-or-state class callbacks. A custom render must preserve semantics, events and refs. |
| children | Wrapper owns the indicator/thumb. Supply visible text through label and supporting content nearby. |
| value | String submitted when checked. |
| indeterminate | false default; exposes mixed state and a visible dash. Caller-controlled mixed state must be updated coherently. |
| parent | false default; use inside CheckboxGroup with allValues to control its aggregate. |
State and keyboard contract
- Space changes a focused mutable checkbox. The label is also a pointer target; do not nest another label around this wrapper.
- Keep checked, mixed graphic and aria-checked consistent. A caller-controlled indeterminate prop is not automatically cleared by changing another state value.
- Disabled prevents interaction; read-only retains the current value. Do not treat either as a permission decision.
- For a required local option, retain the form and focus the control on error; connect the explanation through aria-describedby.
- Names/value/uncheckedValue describe actual submission. An unchecked native checkbox is ordinarily omitted; a boolean React state alone is not FormData.
Failure cases
- A mixed aggregate drawn as an ordinary check.
- An unnamed span checkbox or an incorrectly button-typed public ref.
- Changing disabled/read-only values through a click handler outside the primitive guard.
- A label that changes its meaning after selection, or a form error with no focused recovery.
- Recording consent because an unrelated preference was checked.
Verification and release scope
- Check actual names, roles, state and focus with pointer and keyboard. Inspect the retained value and submitted FormData when relevant.
- Exercise empty, selected, mixed, disabled, read-only, invalid and pending/recovery states that apply to the control. Styling alone does not prove enforcement.
- Check both themes, long labels, 320px reflow, meaningful graphic/text contrast, reduced motion, forced colors and the actual target device.
- Use a named screen-reader/browser pair to verify spoken state. DOM assertions and local examples do not prove announcements or backend persistence.
- Selection never establishes language quality, source authority, consent, qualified review or permission. Keep that evidence explicit in the product task.