MobTranslate styleguide

Toggle Group

Group optional pressed segments with a declared selection policy and keyboard navigation.

Segments and selection policy

Use ToggleGroup for related pressed actions or view segments. This maps the segmented-button family to this library’s web toggle group. The current styling permits wrapping separated rounded segments; it does not claim native Material geometry.

multiple=false permits at most one pressed item and can still permit none. Use RadioGroup or an explicit consumer rule when exactly one choice is required. multiple=true permits independent pressed members. Switching policy must reconcile the retained array deliberately.

Anatomy and specifications

PartContract
RootNamed group, HTMLDivElement ref, horizontal default
MembersNative toggle buttons, stable unique string values
TargetMinimum 44px height; wrapping text and 4px group gaps
ShapeEach segment --radius-md so reflow retains usable boundaries
Orientationvertical lays out a column and changes primitive navigation
StatePressed members expose actual aria-pressed; disabled members remain unavailable

Working local content segments

Local title section

Pressed local sections: title. At most one; empty is allowed.

tsx
'use client';
import { useState } from 'react';
import { Button, Toggle, ToggleGroup, ToggleGroupItem } from '@mobtranslate/ui';

export function ToggleSelectionExample() {
  const [pressed, setPressed] = useState(false);
  return <div data-toggle-lab className="space-y-4"><Toggle pressed={pressed} onPressedChange={setPressed} className={state => state.pressed ? 'local-toggle-on' : 'local-toggle-off'}>Emphasize local example</Toggle><p style={{ fontWeight: pressed ? 700 : 400 }}>This English-only sample changes its actual local emphasis.</p><Toggle disabled>Unavailable emphasis</Toggle><p role="status">Local emphasis {pressed ? 'on' : 'off'}.</p></div>;
}

export function ToggleGroupSelectionExample() {
  const [multiple, setMultiple] = useState(false);
  const [value, setValue] = useState<string[]>(['title']);
  const [vertical, setVertical] = useState(false);
  return <div data-toggle-group-lab className="space-y-4">
    <Button type="button" variant="outline" aria-pressed={multiple} onClick={() => { setMultiple(current => !current); setValue([]); }}>Allow multiple local segments</Button>
    <Button type="button" variant="outline" aria-pressed={vertical} onClick={() => setVertical(current => !current)}>Use vertical local segments</Button>
    <ToggleGroup multiple={multiple} value={value} onValueChange={setValue} orientation={vertical ? 'vertical' : 'horizontal'} aria-label="Local reading sections" className={state => state.multiple ? 'local-segments-multiple' : 'local-segments-single'}>
      <ToggleGroupItem value="title">Title section</ToggleGroupItem><ToggleGroupItem value="notes">Notes section</ToggleGroupItem><ToggleGroupItem value="unavailable" disabled>Unavailable section</ToggleGroupItem>
    </ToggleGroup>
    <div data-local-sections>{value.includes('title') && <h3 className="font-semibold">Local title section</h3>}{value.includes('notes') && <p>English-only local notes section.</p>}{value.length === 0 && <p>No local sections selected.</p>}</div>
    <p role="status">Pressed local sections: {value.join(', ') || 'none'}. {multiple ? 'Multiple choices permitted.' : 'At most one; empty is allowed.'}</p>
  </div>;
}

API reference

Export / propContract
ToggleGroup / ToggleGroupPropsvalue/defaultValue readonly arrays; onValueChange(array, details).
multiplefalse default; true permits several pressed values.
orientation / loopFocushorizontal / true defaults; vertical and non-looping navigation are installed alternatives.
disabledfalse default; root unavailable state applies to members.
ToggleGroupItemInstalled Toggle props/ref; value identifies a member; native button/render props and disabled supported.
Refs / state callbacksGroup div and item button refs; group state includes multiple/disabled; item state includes pressed/disabled.
Form dataNo automatic checkbox/radio payload. Serialize intended values in the consuming form.
className / style / render / native propsInstalled primitive props; styled wrappers retain string-or-state class callbacks. A custom render must preserve semantics, events and refs.

Keyboard and recovery

  • Arrow navigation follows the declared orientation and primitive direction; Tab enters/leaves the composite group. Activation and focus are different states.
  • Skip unavailable items and retain a meaningful focus destination if selected members disappear.
  • Keep group/member names stable and describe the actual displayed content. The example changes real local sections, not only button color.
  • For remote filtering, retain applied/query state and expose loading, empty, error and retry results separately.
  • Changing selection policy clears this local example’s array explicitly; a real application must reconcile it according to its task.

Failure cases

  • Assuming single mode requires exactly one selection.
  • Duplicate values, unnamed groups, or arrow navigation trapped on unavailable members.
  • Vertical behavior with a horizontal-only layout.
  • Form submission that silently omits the selected segments.
  • Pressed color asserted as review approval or successful data filtering.

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.