ComponentsRadioGroup
RadioGroup
Let users choose one option from a visible list.
On this page
Install component source from the public registry, or use the local workspace package. Follow the setup guide first. The npm package is not published yet.
View registry filesImplemented with React and browser APIs, without an additional behavior or rendering library.
Shared foundation: React, clsx for conditional classes, tailwind-merge for resolving utility-class conflicts. Styles use Tailwind CSS and Dethink CSS variables.
Based on this component’s source and registry composition. Libraries used only in showcase examples are excluded.
Import the component into your page or component file.
import { RadioGroup, RadioGroupItem } from "@dethink/components";Try the examples, then open the code to use them in your app. Arrow keys move selection inside a focused group.
With FieldSet anatomy
FieldSet and FieldLegend name the group; each item pairs a RadioGroupItem with a FieldLabel.
Show sourceexamples/radio-group/basic.tsx
"use client";
import {
Field,
FieldControl,
FieldGroup,
FieldLabel,
FieldLegend,
FieldSet,
RadioGroup,
RadioGroupItem,
} from "@dethink/components";
export function RadioGroupBasic() {
return (
<div className="mx-auto max-w-sm">
<FieldSet>
<FieldLegend>Deploy frequency</FieldLegend>
<RadioGroup name="frequency" defaultValue="on-merge">
<FieldGroup>
<Field id="freq-on-merge" orientation="horizontal">
<FieldControl asChild>
<RadioGroupItem value="on-merge" />
</FieldControl>
<FieldLabel>Deploy on every merge</FieldLabel>
</Field>
<Field id="freq-daily" orientation="horizontal">
<FieldControl asChild>
<RadioGroupItem value="daily" />
</FieldControl>
<FieldLabel>Once a day</FieldLabel>
</Field>
<Field id="freq-manual" orientation="horizontal">
<FieldControl asChild>
<RadioGroupItem value="manual" />
</FieldControl>
<FieldLabel>Manual releases only</FieldLabel>
</Field>
</FieldGroup>
</RadioGroup>
</FieldSet>
</div>
);
}Orientation and disabled
orientation lays items out horizontally; group-level disabled flows to every item.
Show sourceexamples/radio-group/states.tsx
"use client";
import {
Field,
FieldControl,
FieldGroup,
FieldLabel,
FieldLegend,
FieldSet,
RadioGroup,
RadioGroupItem,
} from "@dethink/components";
export function RadioGroupStates() {
return (
<div className="mx-auto grid max-w-lg gap-6 sm:grid-cols-2">
<FieldSet>
<FieldLegend>Horizontal</FieldLegend>
<RadioGroup
name="priority"
orientation="horizontal"
defaultValue="high"
>
<FieldGroup className="flex-row gap-4">
<Field id="pri-low" orientation="horizontal">
<FieldControl asChild>
<RadioGroupItem value="low" />
</FieldControl>
<FieldLabel>Low</FieldLabel>
</Field>
<Field id="pri-high" orientation="horizontal">
<FieldControl asChild>
<RadioGroupItem value="high" />
</FieldControl>
<FieldLabel>High</FieldLabel>
</Field>
</FieldGroup>
</RadioGroup>
</FieldSet>
<FieldSet>
<FieldLegend>Disabled group</FieldLegend>
<RadioGroup name="tier" disabled defaultValue="team">
<FieldGroup>
<Field id="tier-starter" orientation="horizontal">
<FieldControl asChild>
<RadioGroupItem value="starter" />
</FieldControl>
<FieldLabel>Starter</FieldLabel>
</Field>
<Field id="tier-team" orientation="horizontal">
<FieldControl asChild>
<RadioGroupItem value="team" />
</FieldControl>
<FieldLabel>Team</FieldLabel>
</Field>
</FieldGroup>
</RadioGroup>
</FieldSet>
</div>
);
}Examples that combine components for common tasks.
Card-style plan picker
Each label grows into a selectable card while the radio stays a real focusable input — so clicks, keyboard movement, and announcements all remain native RadioGroup behavior.
Selected: Team
Show sourceexamples/radio-group/recipe-plan-picker.tsx
"use client";
import { useId, useState } from "react";
import { RadioGroup, RadioGroupItem } from "@dethink/components";
const plans = [
{
value: "starter",
name: "Starter",
price: "$0",
blurb: "Two projects, community support.",
},
{
value: "team",
name: "Team",
price: "$29",
blurb: "Unlimited projects, shared themes.",
},
{
value: "scale",
name: "Scale",
price: "$99",
blurb: "SSO, audit log, priority support.",
},
];
/**
* The radio stays a real, focusable input — the surrounding label just grows
* into a card. Selection styling hangs off the same state via peer classes,
* so keyboard behavior is native RadioGroup behavior.
*/
export function RadioGroupRecipePlanPicker() {
const id = useId();
const [plan, setPlan] = useState("team");
return (
<div className="mx-auto max-w-md space-y-3">
<RadioGroup
name="plan"
aria-label="Pricing plan"
value={plan}
onValueChange={setPlan}
className="grid gap-3"
>
{plans.map((entry) => (
<label
key={entry.value}
htmlFor={`${id}-${entry.value}`}
className={`flex cursor-pointer items-start gap-3 rounded-lg border p-4 transition-colors ${
plan === entry.value
? "border-primary bg-primary/[0.06]"
: "border-border hover:border-primary/40"
}`}
>
<RadioGroupItem
id={`${id}-${entry.value}`}
value={entry.value}
className="mt-1"
/>
<span className="flex-1">
<span className="flex items-baseline justify-between">
<span className="font-heading font-semibold">{entry.name}</span>
<span className="text-muted-foreground text-sm">
{entry.price}/mo
</span>
</span>
<span className="text-muted-foreground mt-1 block text-sm">
{entry.blurb}
</span>
</span>
</label>
))}
</RadioGroup>
<p className="text-muted-foreground text-sm">
Selected: {plans.find((entry) => entry.value === plan)?.name}
</p>
</div>
);
}RadioGroup renders a div with radiogroup semantics; items are real radio inputs.
| Prop | What it does | Default |
|---|---|---|
value / defaultValuestring | Controlled or uncontrolled selected item value. | Not set |
onValueChange(value: string) => void | Fires with the newly selected item's value. | Not set |
namestring | Shared input name applied to every item in the group. | Not set |
orientation"vertical" | "horizontal" | Layout direction and arrow-key axis. | "vertical" |
controlSize"sm" | "md" | "lg" | Control size inherited by every item. | "md" |
required / disabled / readOnly / invalidboolean | Group-level states inherited by every item. | false |
| Prop | What it does | Default |
|---|---|---|
valuestring | Value this radio contributes when selected. | Not set |
controlSize / invalid / disabledper-item overrides | Item-level overrides of the group settings. | inherited |
…native input propsInputHTMLAttributes | Renders a real <input type="radio">. | Not set |