---
title: Choice
description: A radio group for single_choice or a checkbox group for multiple_choice.
preview: choice
---
# Choice

Options as a vertical list of bordered cards, each with a radio or checkbox that Mirio draws (`.mirio-check`), so it follows the theme in light and dark. `Field` renders it for `single_choice` (one value) and `multiple_choice` (an array).

## Preview
<!-- preview: choice -->

## Usage
```tsx
// { key: 'timeline', type: 'single_choice', label: 'When do you want to start?', options: ['This month', 'This quarter', 'Just looking'], required: true }
import { Form } from '@trymirio/mirio';

<Form schema={demo} client={mirio} />

// by hand:
import { Choice } from '@trymirio/mirio';
<Choice name="timeline" optionClassName="py-2" />
```

## Props
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `name` | `string` | required | The field key. |
| `className` | `string` | `flex flex-col gap-2` | Merged onto the group element. |
| `optionClassName` | `string` | option card classes | Merged onto every option `<label>`. |
| …`<div>` attributes | | | Spread on the group element (except `onChange`). |

Rendered structure:

```html
<div id="…" role="radiogroup|group" tabindex="-1" aria-labelledby="…-label" data-mirio="choice" [data-multiple]>
  <label for="…-0" data-mirio="option" [data-checked]>
    <input id="…-0" type="radio|checkbox" class="mirio-check" name="timeline" value="This month" />
    <span>This month</span>
  </label>
  …
</div>
```

The group element carries the field id, so error focus lands on it, and `data-multiple` is present for checkbox groups. `multiple_choice` honours `minSelected`/`maxSelected` on submit.

## Variants and states
Checkbox group:

<!-- preview: choice-multiple -->

```tsx
// { key: 'channels', type: 'multiple_choice', label: 'How did you hear about us?', help: 'Pick up to two.', options: ['Search', 'A friend', 'Social media', 'Newsletter'], maxSelected: 2 }
<Form schema={survey} client={mirio} />
```

- **Checked**: the option card gets `data-checked` and a `--mirio-ring` border (`has-[:checked]:border-mirio-ring`).
- **Hover**: cards tint with `--mirio-muted`.
- **Error**: the group gets `aria-invalid`; the message renders below the group.
- **Prefill**: `defaultValue: 'This month'` or, for multiple, `defaultValue: ['Search']`.

## Customization
- Tokens: `--mirio-border`, `--mirio-bg`, `--mirio-muted` (hover), `--mirio-ring` (checked border and focus ring), `--mirio-input-bg` and `--mirio-border` (the unchecked box), `--mirio-primary` and `--mirio-primary-fg` (the checked box and its check or dot), `--mirio-radius`.
- Horizontal options: `className="flex-row flex-wrap"`. Pills: `optionClassName="rounded-full py-1.5"`.
- CSS hooks: `[data-mirio="choice"]`, `[data-mirio="choice"][data-multiple]`, `[data-mirio="option"][data-checked]`, and the box itself: `.mirio-check`, `.mirio-check:checked`, `.mirio-check::before` (the check or dot). The box is centered on the first line of its label.
- The whole option card is the `<label>`, so clicking anywhere on it toggles the input.

## Accessibility
`role="radiogroup"` (single) or `role="group"` (multiple) labelled by the field label through `aria-labelledby`; help and error through `aria-describedby`; each option is a real `<input>` with its own `<label>`, so arrow keys (radios) and Space work natively and screen readers announce "1 of 3".

## Related
[Select](/docs/components/select.md), [YesNo](/docs/components/yes-no.md), [Field](/docs/components/field.md), [Schema: field types](/docs/schema.md)
