Checkbox Group
Share a set of independent values and derive a select-all state from the actual members.
Group related independent choices
Use a named group when choices share a question but are independently selectable. A fieldset/legend supplies useful structure; a parent checkbox may summarize none, some or all selected members.
Pass every available member value through allValues when using parent. Do not include the parent as a member value or infer the aggregate from color. For dynamic lists, derive the available values from the actual data and reconcile removed/unavailable members.
Anatomy and layout
| Part | Contract |
|---|---|
| Root | Div with shared checkbox value state; vertical flex and 8px gaps |
| Question | Caller-owned fieldset/legend and a coherent group name |
| Parent | Checkbox parent; ordinary/mixed aggregate with a visible check/dash |
| Members | Checkbox values belong to allValues; own names determine form serialization |
| Unavailable | Root disabled is inherited by members and parent |
| Long labels | 44px label targets can grow to multiple lines; preserve reading order |
Working aggregate and grouped submission
Selected local formats: text. No local grouped form 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 |
|---|---|
| value / defaultValue | Controlled / initial string[] of selected member values. |
| onValueChange | (string[], eventDetails); update the actual retained selected set. |
| allValues | string[] of every member; required for a meaningful parent aggregate. |
| disabled | false default; shared unavailable state. |
| ref / children | HTMLDivElement ref; caller renders labelled members and optional parent. |
| 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. |
| Form payload | Group does not supply a name field; give actual child checkboxes name/value and inspect getAll(name). |
Behavior and recovery
- Parent selection applies to the real members; its mixed state follows partial selection. Verify the installed cycle rather than assuming that a prior partial set is restored.
- Keep the question and aggregate understandable when a result set changes. Do not silently apply a selection to hidden/unavailable items.
- If a server rejects a bulk action, retain the intended set and show which outcomes actually occurred.
- Group selection is distinct from selecting a range, choosing one option or granting permission to use source media.
Failure cases
- A stale allValues array that includes removed data or omits visible items.
- A select-all checkbox submitted as an extra member.
- Calling the group selected while some members remain unchecked.
- Claiming that a submitted UI selection means a batch operation succeeded.
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.