Fields
Radio
A set of choices where one choice wins.
When to use it: Use it for one choice from a short visible list.
Example
import { Radio, RadioGroup } from "@comp0/react";
export function Example() {
return (
<RadioGroup
className="flex flex-col gap-2 sm:flex-row sm:gap-4"
defaultValue="standard"
name="shipping"
>
<Radio
value="standard"
className="group flex min-h-7 items-center gap-2 rounded px-1 text-base text-zinc-800 data-selected:bg-teal-50 data-selected:text-teal-950 sm:text-sm dark:text-zinc-100 dark:data-selected:bg-teal-950/40 dark:data-selected:text-teal-50"
>
<span className="grid size-5 shrink-0 place-items-center rounded-full border border-zinc-950/20 bg-white ring-2 ring-transparent group-data-focused:ring-teal-600 group-data-selected:border-teal-600 sm:size-4 dark:border-white/20 dark:bg-zinc-900 dark:group-data-focused:ring-teal-400 dark:group-data-selected:border-teal-400">
<span className="size-2 rounded-full bg-teal-600 opacity-0 group-data-selected:opacity-100 dark:bg-teal-400" />
</span>
Standard
</Radio>
<Radio
value="express"
className="group flex min-h-7 items-center gap-2 rounded px-1 text-base text-zinc-800 data-selected:bg-teal-50 data-selected:text-teal-950 sm:text-sm dark:text-zinc-100 dark:data-selected:bg-teal-950/40 dark:data-selected:text-teal-50"
>
<span className="grid size-5 shrink-0 place-items-center rounded-full border border-zinc-950/20 bg-white ring-2 ring-transparent group-data-focused:ring-teal-600 group-data-selected:border-teal-600 sm:size-4 dark:border-white/20 dark:bg-zinc-900 dark:group-data-focused:ring-teal-400 dark:group-data-selected:border-teal-400">
<span className="size-2 rounded-full bg-teal-600 opacity-0 group-data-selected:opacity-100 dark:bg-teal-400" />
</span>
Express
</Radio>
</RadioGroup>
);
}Plan cards
Turn each radio into a full-width card with supporting details and price.
import { Radio, RadioGroup } from "@comp0/react";
const plans = [
{
value: "starter",
name: "Starter",
description: "For personal projects and prototypes.",
price: "$0",
},
{
value: "pro",
name: "Pro",
description: "For teams shipping multiple products.",
price: "$12",
},
{
value: "business",
name: "Business",
description: "For organizations that need advanced controls.",
price: "$49",
},
];
export function Example() {
return (
<RadioGroup className="m-0 w-full max-w-md border-0 p-0" defaultValue="pro" name="plan">
<legend className="mb-3 text-base font-medium text-zinc-950 sm:text-sm dark:text-white">
Choose a plan
</legend>
<div className="grid gap-3">
{plans.map((plan) => (
<Radio
key={plan.value}
value={plan.value}
className="group grid cursor-pointer grid-cols-[auto_1fr_auto] items-start gap-3 rounded-xl border border-zinc-950/10 bg-white p-4 text-left text-zinc-800 outline-2 outline-offset-2 outline-transparent transition hover:border-zinc-950/20 data-focused:outline-teal-600 data-selected:border-teal-600 data-selected:bg-teal-50 dark:border-white/10 dark:bg-zinc-900 dark:text-zinc-100 dark:hover:border-white/20 dark:data-focused:outline-teal-400 dark:data-selected:border-teal-400 dark:data-selected:bg-teal-950/40"
>
<span
aria-hidden="true"
className="mt-0.5 grid size-5 shrink-0 place-items-center rounded-full border border-zinc-950/20 bg-white group-data-selected:border-teal-600 dark:border-white/20 dark:bg-zinc-900 dark:group-data-selected:border-teal-400"
>
<span className="size-2 rounded-full bg-teal-600 opacity-0 group-data-selected:opacity-100 dark:bg-teal-400" />
</span>
<span className="grid gap-1">
<span className="font-medium text-zinc-950 dark:text-white">{plan.name}</span>
<span className="text-base text-zinc-600 sm:text-sm dark:text-zinc-400">
{plan.description}
</span>
</span>
<span className="font-medium text-zinc-950 dark:text-white">{plan.price}</span>
</Radio>
))}
</div>
</RadioGroup>
);
}Anatomy
Dashed frames are invisible state providers; shaded shapes own real DOM. Numbered pins match the list below.
A wireframe sketch of the assembled component. Each numbered marker matches a part in the list that follows.
RadioGroup
Selected-value provider; no DOM by default. Does not add a DOM element.
Radio
Labelled native radio option. Owns a DOM element.
Step by step
- 1
Add the main part
Start RadioGroup with a name.
- 2
Add the supporting parts
Add one Radio per choice, each with a different value.
- 3
Make the behavior clear
Use defaultValue to show the starting choice.
Exampletsx <RadioGroup name="plan" defaultValue="pro"><Radio value="pro">Pro</Radio></RadioGroup>
Keyboard
- ↓
- Moves to the next radio.
- ↑
- Moves to the previous radio.
- Space
- Selects the focused radio.
Forms and accessibility
The selected radio submits RadioGroup.name and its value.
Accessibility checklist
- Name the group with a Legend or aria-label.
- Use radio only when one choice is allowed.
- Keep every option label easy to click and read.
API reference
import { Radio, RadioGroup } from "@comp0/react";RadioGroup
Context onlySelected-value provider; no DOM by default.
| Prop | Type | Description |
|---|---|---|
value | string | Controlled choice. |
defaultValue | string | Initial choice. |
onChange | (value: string) => void | Receives the next choice. |
name | string | Shared submission name for the group. |
required | boolean | Requires one radio in the group to be selected. |
Radio
DOM elementLabelled native radio option.
| Prop | Type | Description |
|---|---|---|
value | string | This option’s value. |
checked / defaultChecked | boolean | Controlled or initial standalone state. |
disabled | boolean | Disables the option. |
Style hooks
Attributes that appear while a state is true. Target them with Tailwind data variants such as data-open:bg-zinc-100, or with any CSS selector.
[data-selected]on Radio
- This radio is selected.
[data-disabled]on Radio
- This radio is disabled.