Radio group
NewOne choice out of a few, with every option in view: what happens when stock runs out, how long customers have to return an item. Options are rows beside their labels, or cards for settings that choose between richer options.
Playground
Change the props, then choose with the pointer, or Tab in and use the arrow keys. The code shows the group's own props; inside it are three RadioGroupItems, each with a label and, when switched on, a description and a hint.
<RadioGroup aria-label="Delivery speed" defaultValue="next-day" />Basics
A RadioGroup holds a RadioGroupItem per option, each with a value and a label. A description under the label explains what the choice does. Put the group in a Field to name it, and control it with value and onValueChange.
Cards
variant="card" turns each option into a tile, for settings that choose between richer options. The whole tile is the radio: clicking anywhere on it chooses, its edge takes the teal when chosen, and the focus ring goes round the tile. A label can carry a small Badge, as here.
Applies to every checkout on Harbour Desk Co.
Hints
A hint sits at the end of the option: a price, a fee, "Free". In a list the row then spans the group, so the hints line up; on a card it is set firmer, in ink.
Horizontal
orientation="horizontal" sets short options in a row that wraps when it runs out of room. Cards in a row share the width equally, and fall into a column on a phone. The arrow keys move between options whichever way they run.
Images are cropped to fit, from the centre.
In a field, with an error
Inside a Field, the group is named by the Field's label and described by its hint or error. An error rings the radios in red and sets aria-invalid on the group.
Start with nothing chosen only when there is no safe default, and check on save. Press Save to see the error, then choose a window to clear it.
States
Unchosen and chosen, an option that is off (disabled on the item, with the reason in its description), a whole group that is off, and an invalid group. Hover firms the ring; pressing sinks the radio a little; the dot drops in on the pop spring.
Long labels
Labels and descriptions wrap beside the radio, which stays on the first line.
Your own labels
Without a label, an item is just the radio, for a layout of your own: give it an id and point a <label htmlFor> at it (or give it an aria-label). Here the last option shows a field of its own once it is chosen.
Sizes
md radios are 16px (default) and sm 14px, the Checkbox sizes; the hit area is at least 24px either way. Small cards also tighten their padding.
In both themes
A list with a chosen option and one that is off, and cards with a chosen tile, light and dark side by side.
Guidelines
- Write labels as the outcome, in sentence case, and keep them parallel: "Stop selling", "Keep selling", "Hide it".
- Order options by how often they are chosen, or by a natural order (fastest to slowest, smallest to largest). Put a safe default first and choose it.
- Use descriptions for consequences people would otherwise have to guess, not to repeat the label.
- Changing a radio should not save on its own: settings with radios apply when the form is saved.
| To choose | Use |
|---|---|
| One of two to five, applied when the form is saved | Radio group |
| One of a few views or ranges, applied at once | Segmented control |
| One of many, or a list that grows | Select |
| Several independent choices | Checkbox |
| On or off, straight away | Switch |
Accessibility
- Built on Radix RadioGroup: the group is one tab stop, landing on the chosen option. The arrow keys move between options and choose as they go; disabled options are skipped.
- Name the group: put it in a
Field(the group pointsaria-labelledbyat the Field's label), or passaria-labeloraria-labelledby. - Each radio is named by its label alone, and its description and hint are read after it.
- A hidden radio input goes with each option, so
nameandrequiredwork in forms, and resetting the form brings back the starting value. - The hit area round a 16px radio is at least 24px square, and clicking the label chooses too. A card is all hit area.
- Focus shows as a teal ring round the radio, or round the whole card.
- Under reduced motion the dot fades in at full size, and nothing sinks when pressed.
Props
RadioGroup
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | null | None | Controlled value. null for no choice yet. |
defaultValue | string | None | Starting value when uncontrolled. Leave it out to start with nothing chosen. |
onValueChange | (value: string) => void | None | Called with the chosen value. A typed value keeps it typed. |
variant | 'list' | 'card' | 'list' | Radios beside labels, or a tile per option. |
orientation | 'vertical' | 'horizontal' | 'vertical' | A column, or a row that wraps. Cards in a row share the width. |
size | 'sm' | 'md' | 'md' | 14 or 16px radios. |
invalid | boolean | None | Red rings and aria-invalid. In a Field, follows its error. |
disabled | boolean | false | Every option off. |
name, required, form | string, boolean, string | None | For the hidden radio inputs that forms submit. |
RadioGroupItem
| Prop | Type | Default | Description |
|---|---|---|---|
valueRequired | string | None | Unique within the group. |
label | ReactNode | None | The option's name. Without it, the item is the bare radio, for a label of your own. |
description | ReactNode | None | A quieter line under the label. |
hint | ReactNode | None | Trailing detail, such as a price. |
variant | 'list' | 'card' | None | This option's look, whatever the group's variant. |
id | string | None | The radio button's id, for a <label htmlFor> of your own. Generated when omitted. |
disabled | boolean | None | This option off. Say why in its description. |
On an item, ref and native props go to the radio button; className and style go to the outer element (the card itself, for a card).