MobTranslate styleguide

Checkbox Group

Share a set of independent values and derive a select-all state from the actual members.

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

PartContract
RootDiv with shared checkbox value state; vertical flex and 8px gaps
QuestionCaller-owned fieldset/legend and a coherent group name
ParentCheckbox parent; ordinary/mixed aggregate with a visible check/dash
MembersCheckbox values belong to allValues; own names determine form serialization
UnavailableRoot disabled is inherited by members and parent
Long labels44px label targets can grow to multiple lines; preserve reading order

Working aggregate and grouped submission

Local resource format filters

Selection describes this sample, not permission to use any recording or image.

Selected local formats: text. No local grouped form submitted.

tsx
'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

PropContract
value / defaultValueControlled / initial string[] of selected member values.
onValueChange(string[], eventDetails); update the actual retained selected set.
allValuesstring[] of every member; required for a meaningful parent aggregate.
disabledfalse default; shared unavailable state.
ref / childrenHTMLDivElement ref; caller renders labelled members and optional parent.
className / style / render / native propsInstalled primitive props; styled wrappers retain string-or-state class callbacks. A custom render must preserve semantics, events and refs.
Form payloadGroup 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.