# Checkboxes, radios and switches

Selection groups with explicit controlled state and native labels.

## Quick start

After the [application setup](/docs/installation/#component-consumption), copy this component into your application. It imports only public packages; the complete live demo later on this page also uses documentation layout helpers.

```tsx
'use client';
import { useState } from 'react';
import { Radio } from '@fayegram-ai/faye-ui/controls/radio';
import { RadioGroup } from '@fayegram-ai/faye-ui/controls/radio-group';
import { Switch } from '@fayegram-ai/faye-ui/controls/switch';
import { ControlLabel } from '@fayegram-ai/faye-ui/controls/field-layout';
import { Stack } from '@fayegram-ai/faye-ui/layout/layout';

export function Preferences() {
  const [pace, setPace] = useState('slow');
  const [notifications, setNotifications] = useState(true);
  return <Stack gap="4">
    <RadioGroup name="pace" aria-label="Your pace" value={pace} onValueChange={value => setPace(String(value))}>
      <Stack gap="3">{['slow', 'adventurous'].map(value => <ControlLabel key={value} label={value}>
        {attributes => <Radio.Root {...attributes} value={value}><Radio.Indicator /></Radio.Root>}
      </ControlLabel>)}</Stack>
    </RadioGroup>
    <ControlLabel label="Enable notifications" description="Hear about new places.">
      {attributes => <Switch.Root {...attributes} checked={notifications} onCheckedChange={setNotifications}>
        <Switch.Thumb />
      </Switch.Root>}
    </ControlLabel>
  </Stack>;
}
```

API reference: [Radio](/docs/api-controls-radio/), [Radio Group](/docs/api-controls-radio-group/), [Switch](/docs/api-controls-switch/), [Field Layout](/docs/api-controls-field-layout/).

## Usage contract
Checkboxes model independent selections. Radio groups model exclusive choices. Switches represent immediate on/off settings. Disabled, indeterminate and read-only are separate states. The examples supply labels instead of relying on position or color.

Read-only checkboxes, radios and switches retain their value and keyboard focus indicator without advertising a change through hover or press effects. Their controls and `ControlLabel` labels use a neutral cursor. Set `readOnly` on a radio group or an individual choice; use `disabled` when the control must also leave keyboard interaction. Disabled appearance takes precedence when both states apply.

## Customization
Use semantic tokens for visual changes, public component parts for structural customization, and explicit props for instance behavior. The surrounding site lets you compare light and dark, different brands and density.

## Source and verification
The examples below import actual public package entries. Full source is downloadable. Source coverage does not establish keyboard, hydration or browser compatibility; those require the application test suite.

## Executable example source

```tsx
'use client';
import { Check, Minus } from '@lucide/icons';
import { Icon } from '@fayegram-ai/faye-ui/core/icon';
import { useState } from 'react';
import { Checkbox } from '@fayegram-ai/faye-ui/controls/checkbox';
import { CheckboxGroup } from '@fayegram-ai/faye-ui/controls/checkbox-group';
import { Radio } from '@fayegram-ai/faye-ui/controls/radio';
import { RadioGroup } from '@fayegram-ai/faye-ui/controls/radio-group';
import { Switch } from '@fayegram-ai/faye-ui/controls/switch';
import { ControlLabel } from '@fayegram-ai/faye-ui/controls/field-layout';
import { ThemeScope } from '@fayegram-ai/faye-ui/provider/provider';
import { Stack, Inline } from '@fayegram-ai/faye-ui/layout/layout';
import { Section, Result } from '../example';
export default function Selection() {
    const [channels, setChannels] = useState<string[]>(['email']), [frequency, setFrequency] = useState('weekly'), [enabled, setEnabled] = useState(true);
    return <><Section title="Checkbox groups"><CheckboxGroup value={channels} onValueChange={setChannels} aria-label="Notification channels"><Stack gap="3">{['email', 'inbox', 'mobile'].map(value => <ControlLabel key={value} label={value}>{attrs => <Checkbox.Root {...attrs} value={value}><Checkbox.Indicator><Icon icon={Check} size="xs"/></Checkbox.Indicator></Checkbox.Root>}</ControlLabel>)}</Stack></CheckboxGroup><Inline wrap gap="5"><ControlLabel label="Mixed state">{attrs => <Checkbox.Root {...attrs} indeterminate><Checkbox.Indicator><Icon icon={Minus} size="xs"/></Checkbox.Indicator></Checkbox.Root>}</ControlLabel><ControlLabel label="Disabled" disabled>{attrs => <Checkbox.Root {...attrs} defaultChecked><Checkbox.Indicator><Icon icon={Check} size="xs"/></Checkbox.Indicator></Checkbox.Root>}</ControlLabel></Inline><Result>Channels: {channels.join(', ') || 'none'}</Result></Section>
    <Section title="Mutually exclusive options"><RadioGroup value={frequency} onValueChange={value => setFrequency(String(value))} aria-label="Digest frequency"><Stack gap="3">{['daily', 'weekly', 'never'].map(value => <ControlLabel key={value} label={value} description={value === 'weekly' ? 'A weekly selection of places and stories, picked for you.' : undefined}>{attrs => <Radio.Root {...attrs} value={value}><Radio.Indicator/></Radio.Root>}</ControlLabel>)}</Stack></RadioGroup><Result>Frequency: {frequency}</Result></Section>
    <Section title="Immediate state changes"><ControlLabel label="Enable notifications" description="Hear about new places without losing your current view.">{attrs => <Switch.Root {...attrs} checked={enabled} onCheckedChange={setEnabled}><Switch.Thumb/></Switch.Root>}</ControlLabel><Result>Notifications {enabled ? 'enabled' : 'disabled'}</Result><ThemeScope direction="ltr"><ControlLabel label="Nested left-to-right switch">{attrs => <Switch.Root {...attrs} defaultChecked variants={{size: 'sm'}}><Switch.Thumb/></Switch.Root>}</ControlLabel></ThemeScope></Section></>;
}
```
