# Checkbox and radio

> Checkboxes whose check draws itself in (an indeterminate dash retracts into it) and radios whose dot pops into the center, inline or as list rows, with CheckboxGroup and RadioGroup helpers.

- Type: Component (inputs)
- Access: Free, open source (MIT)
- Page: https://goodui.dev/native/components/checkbox
- Markdown: https://goodui.dev/native/components/checkbox.md
- Registry item: https://goodui.dev/native/r/checkbox.json
- Dependencies: react-native-reanimated, react-native-svg
- Keywords: react native checkbox, radio button, radio group, checkbox list, indeterminate checkbox, select all, animated checkbox, terms and conditions checkbox

## When to use

- Several independent choices in a form or filter: amenities, toppings, notification types (CheckboxGroup).
- One choice out of a few that should all be visible: delivery speed, plan, payment method (RadioGroup).
- Accepting terms before continuing (Checkbox with invalid when skipped).
- A select-all checkbox that shows a partial selection (checked='indeterminate').

## When not to use

- A setting that takes effect immediately: use switch.
- Two to five views of the same data: use segmented-control.
- Long option lists: use a searchable list with search-bar.

## Installation

With the shadcn CLI (needs a components.json in the Expo app), then `npx expo install --fix` so native dependencies match your Expo SDK. The foundation comes along the first time.

```bash
npx shadcn@latest add https://goodui.dev/native/r/checkbox.json
npx expo install --fix
```

The component lands in `components/goodui-native/checkbox/` and imports the foundation from `components/goodui-native/foundation/` with relative paths.

## Usage

```tsx
import { useState } from "react";
import { Checkbox, RadioGroup } from "@/components/goodui-native/checkbox/checkbox";

export function Checkout() {
  const [speed, setSpeed] = useState<"standard" | "express">("standard");
  const [terms, setTerms] = useState(false);
  return (
    <>
      <RadioGroup
        accessibilityLabel="Delivery"
        value={speed}
        onValueChange={setSpeed}
        options={[
          { value: "standard", label: "Standard", description: "3 to 5 days, free" },
          { value: "express", label: "Express", description: "Tomorrow, $9.90" },
        ]}
      />
      <Checkbox label="I accept the terms of service" checked={terms} onCheckedChange={setTerms} />
    </>
  );
}
```

## Examples

### Select all with indeterminate

```tsx
const all = picked.length === items.length;
<Checkbox
  label="Select all"
  checked={all ? true : picked.length ? "indeterminate" : false}
  onCheckedChange={(on) => setPicked(on ? items.map((i) => i.id) : [])}
/>
```

### Settings-style rows with trailing checkboxes

```tsx
<CheckboxGroup
  position="trailing"
  value={channels}
  onValueChange={setChannels}
  options={[
    { value: "email", label: "Email" },
    { value: "sms", label: "Text message" },
    { value: "push", label: "Push" },
  ]}
/>
```

## API reference

### Checkbox

`import { Checkbox } from "@/components/goodui-native/checkbox/checkbox";`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `label` | `string` | – | Visible label. The whole label area toggles. |
| `description` | `string` | – | Secondary line under the label. |
| `variant` | `"row" \| "inline"` | – | `inline` hugs its content; `row` is a full-width list row for settings and grouped lists. |
| `position` | `"leading" \| "trailing"` | – | Where the control sits. Default `leading`; `trailing` matches iOS settings lists. |
| `size` | `"sm" \| "md"` | – | Box 18 (`sm`) or 22 pt (`md`). The touch target stays 44 pt. |
| `tone` | `"success" \| "danger" \| "accent"` | – | Fill color when selected. `accent` by default. |
| `invalid` | `boolean` | – | Red border, for a required choice that was skipped (for example terms not accepted). |
| `disabled` | `boolean` | `false` | Dims the control and ignores presses. |
| `haptic` | `boolean` | `true` | `false` silences the select haptic. |
| `accessibilityLabel` | `string` | – | Screen reader label when there is no visible `label`. |
| `style` | `StyleProp<ViewStyle>` | – | Style for the outer container. |
| `testID` | `string` | – | Identifier for end-to-end tests. |
| `checked` | `false \| true \| "indeterminate"` | – | `true`, `false` or `"indeterminate"` (controlled). Pair it with `onCheckedChange`. |
| `defaultChecked` | `false \| true \| "indeterminate"` | `false` | Initial state when uncontrolled. |
| `onCheckedChange` | `(checked: boolean) => void` | – | Called with the new state. Pressing an indeterminate box checks it. |

### Radio

`import { Radio } from "@/components/goodui-native/checkbox/checkbox";`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `label` | `string` | – | Visible label. The whole label area toggles. |
| `description` | `string` | – | Secondary line under the label. |
| `variant` | `"row" \| "inline"` | – | `inline` hugs its content; `row` is a full-width list row for settings and grouped lists. |
| `position` | `"leading" \| "trailing"` | – | Where the control sits. Default `leading`; `trailing` matches iOS settings lists. |
| `size` | `"sm" \| "md"` | – | Box 18 (`sm`) or 22 pt (`md`). The touch target stays 44 pt. |
| `tone` | `"success" \| "danger" \| "accent"` | – | Fill color when selected. `accent` by default. |
| `invalid` | `boolean` | – | Red border, for a required choice that was skipped (for example terms not accepted). |
| `disabled` | `boolean` | `false` | Dims the control and ignores presses. |
| `haptic` | `boolean` | `true` | `false` silences the select haptic. |
| `accessibilityLabel` | `string` | – | Screen reader label when there is no visible `label`. |
| `style` | `StyleProp<ViewStyle>` | – | Style for the outer container. |
| `testID` | `string` | – | Identifier for end-to-end tests. |
| `selected` | `boolean` | `false` | Selected (controlled). A radio is only ever selected by pressing it, never unselected. |
| `onSelect` | `() => void` | – | Called when the radio is pressed while not selected. |

### CheckboxGroup

`import { CheckboxGroup } from "@/components/goodui-native/checkbox/checkbox";`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `options` (required) | `ChoiceOption<T>[]` | – | The choices. |
| `variant` | `"row" \| "inline"` | `"row"` | `row` (default) draws full-width rows separated by hairlines; `inline` stacks compact items. |
| `position` | `"leading" \| "trailing"` | – | Where each control sits. |
| `size` | `"sm" \| "md"` | – | – |
| `tone` | `"success" \| "danger" \| "accent"` | – | – |
| `disabled` | `boolean` | – | Dims the control and ignores presses. |
| `haptic` | `boolean` | – | `false` silences the select haptics. |
| `accessibilityLabel` | `string` | – | Screen reader name of the group, for example "Delivery speed". |
| `style` | `StyleProp<ViewStyle>` | – | Style for the outer container. |
| `testID` | `string` | – | Identifier for end-to-end tests. |
| `value` | `T[]` | – | Checked values (controlled). |
| `defaultValue` | `T[]` | `[]` | Initially checked values when uncontrolled. |
| `onValueChange` | `(value: T[]) => void` | – | Called with the new list of checked values, in option order. |

### RadioGroup

`import { RadioGroup } from "@/components/goodui-native/checkbox/checkbox";`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `options` (required) | `ChoiceOption<T>[]` | – | The choices. |
| `variant` | `"row" \| "inline"` | `"row"` | `row` (default) draws full-width rows separated by hairlines; `inline` stacks compact items. |
| `position` | `"leading" \| "trailing"` | – | Where each control sits. |
| `size` | `"sm" \| "md"` | – | – |
| `tone` | `"success" \| "danger" \| "accent"` | – | – |
| `disabled` | `boolean` | – | Dims the control and ignores presses. |
| `haptic` | `boolean` | – | `false` silences the select haptics. |
| `accessibilityLabel` | `string` | – | Screen reader name of the group, for example "Delivery speed". |
| `style` | `StyleProp<ViewStyle>` | – | Style for the outer container. |
| `testID` | `string` | – | Identifier for end-to-end tests. |
| `value` | `T` | – | Selected value (controlled). |
| `defaultValue` | `T` | – | Initially selected value when uncontrolled. |
| `onValueChange` | `(value: T) => void` | – | Called with the newly selected value. |

## Accessibility

- Each item is a checkbox or radio (accessibilityRole) whose label is 'label, description'. Checkboxes report checked true, false or mixed; radios report checked and selected.
- RadioGroup is a radiogroup labelled by accessibilityLabel.
- Without a visible label pass accessibilityLabel; the bare control then gets hitSlop up to 44 pt. Labelled items are at least 44 pt tall.

## Motion

- Check: the fill closes in from 40 % on the snappy spring, then the check stroke draws itself in 240 ms.
- Checked to indeterminate (and back): one stroke retracts while the other draws.
- Radio: the fill closes in and the center dot pops.
- Press: the box scales to 0.88 from touch down; rows also fill with a muted background.
- Reduced motion: no scaling; fill, stroke and dot appear with a short fade-speed timing.

## Haptics

- pop when a checkbox is checked, tick when it is unchecked or a radio is chosen (a radio that is already selected does nothing)

## Performance

- The stroke is drawn with an animated strokeDashoffset on the UI thread; a change is one React update.

## Notes for AI

- Checkbox: checked + onCheckedChange (or defaultChecked). onCheckedChange always receives a boolean; pressing an indeterminate box checks it.
- Radio has no unselect. Prefer RadioGroup with options over wiring Radio by hand.
- CheckboxGroup returns values in option order, not tap order.
- variant='row' (the group default) is for lists on a surface without padding; variant='inline' for compact forms.
- Use invalid to mark a required checkbox that was skipped, with the reason in text near it.

## Related

- [Switch](https://goodui.dev/native/components/switch.md): An iOS-style switch with squash and stretch: the thumb stretches toward where it is going on touch down, then travels on a spring while the track color follows. Settings-row layout, tones, sizes and optimistic async changes.
- [Segmented control](https://goodui.dev/native/components/segmented-control.md): Two to five views of the same thing with an elastic highlight that stretches toward the new segment, can be dragged, and ticks at every boundary. Includes a billing toggle.
- [Text field](https://goodui.dev/native/components/text-field.md): A text field whose label glides up as you focus it, with helper and error text that slide in, a shake and haptic on error, clear and reveal buttons, a character counter and a multiline mode that grows with its text.

## Guidance for AI tools

Checkbox and radio: Checkboxes whose check draws itself in (an indeterminate dash retracts into it) and radios whose dot pops into the center, inline or as list rows, with CheckboxGroup and RadioGroup helpers. Use only the documented props. Keep accessibility, reduced motion and both color schemes intact when adapting it.

Full library index: https://goodui.dev/native/llms.txt
