Radio Group
Sharp radio group with a signal indicator.
"use client";import { Label } from "@/registry/okibi/ui/label";import { RadioGroup, RadioGroupItem,} from "@/registry/okibi/ui/radio-group";export default function RadioGroupDemo() { return ( <RadioGroup defaultValue="standby"> <div className="flex items-center gap-2"> <RadioGroupItem id="radio-demo-standby" value="standby" /> <Label htmlFor="radio-demo-standby">Standby</Label> </div> <div className="flex items-center gap-2"> <RadioGroupItem id="radio-demo-active" value="active" /> <Label htmlFor="radio-demo-active">Active</Label> </div> </RadioGroup> );}npx shadcn@latest add https://okibi.cndr.dev/r/radio-group.jsonInstallation
Install the theme first, then add the component:
npx shadcn@latest add https://okibi.cndr.dev/r/radio-group.jsonInstall the required dependencies:
npm install @radix-ui/react-radio-group lucide-reactCopy the source from the Code tab above into components/ui/radio-group.tsx. It uses the cn helper and the okibi theme tokens — install the theme first if you haven't.
Usage
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
<RadioGroup defaultValue="alpha">
<RadioGroupItem value="alpha" id="r-alpha" />
<RadioGroupItem value="bravo" id="r-bravo" />
</RadioGroup>Anatomy
<RadioGroup defaultValue="alpha">
{/* one row per choice */}
<div className="flex items-center gap-2">
<RadioGroupItem value="alpha" id="r-alpha" />
<Label htmlFor="r-alpha">Alpha</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="bravo" id="r-bravo" />
<Label htmlFor="r-bravo">Bravo</Label>
</div>
</RadioGroup>RadioGroup— therole='radiogroup'container; owns the selectedvalue.RadioGroupItem— one square option; itsvalueis what the group reports when checked.Label— paired byhtmlForto each item'sidfor the accessible name and click target.
Examples
Disabled
Disable a single RadioGroupItem to lock one option, or disable the root to lock the whole group.
"use client";import { Label } from "@/registry/okibi/ui/label";import { RadioGroup, RadioGroupItem } from "@/registry/okibi/ui/radio-group";export default function RadioGroupDisabledDemo() { return ( <RadioGroup defaultValue="standby"> <div className="flex items-center gap-2"> <RadioGroupItem id="radio-disabled-standby" value="standby" /> <Label htmlFor="radio-disabled-standby">Standby</Label> </div> <div className="flex items-center gap-2"> <RadioGroupItem id="radio-disabled-active" value="active" /> <Label htmlFor="radio-disabled-active">Active</Label> </div> <div className="flex items-center gap-2"> <RadioGroupItem id="radio-disabled-lockdown" value="lockdown" disabled /> <Label htmlFor="radio-disabled-lockdown">Lockdown</Label> </div> </RadioGroup> );}With descriptions
Pair each option with a Label and a muted helper line for settings-style rows.
Suppress non-critical alerts and run dark.
Route signals normally with standard thresholds.
Escalate every anomaly and pre-arm countermeasures.
"use client";import { Label } from "@/registry/okibi/ui/label";import { RadioGroup, RadioGroupItem } from "@/registry/okibi/ui/radio-group";export default function RadioGroupTextDemo() { return ( <RadioGroup defaultValue="balanced" className="gap-4"> <div className="flex items-start gap-3"> <RadioGroupItem id="radio-text-quiet" value="quiet" className="mt-0.5" /> <div className="space-y-1"> <Label htmlFor="radio-text-quiet">Quiet mode</Label> <p className="text-xs text-muted-foreground"> Suppress non-critical alerts and run dark. </p> </div> </div> <div className="flex items-start gap-3"> <RadioGroupItem id="radio-text-balanced" value="balanced" className="mt-0.5" /> <div className="space-y-1"> <Label htmlFor="radio-text-balanced">Balanced</Label> <p className="text-xs text-muted-foreground"> Route signals normally with standard thresholds. </p> </div> </div> <div className="flex items-start gap-3"> <RadioGroupItem id="radio-text-aggressive" value="aggressive" className="mt-0.5" /> <div className="space-y-1"> <Label htmlFor="radio-text-aggressive">Aggressive</Label> <p className="text-xs text-muted-foreground"> Escalate every anomaly and pre-arm countermeasures. </p> </div> </div> </RadioGroup> );}Invalid
Set aria-invalid on the group to flip the items to the destructive keyline for validation errors.
SEC // select an operating posture to continue.
"use client";import { Label } from "@/registry/okibi/ui/label";import { RadioGroup, RadioGroupItem } from "@/registry/okibi/ui/radio-group";export default function RadioGroupInvalidDemo() { return ( <div className="space-y-2"> <RadioGroup aria-invalid> <div className="flex items-center gap-2"> <RadioGroupItem id="radio-invalid-standby" value="standby" aria-invalid /> <Label htmlFor="radio-invalid-standby">Standby</Label> </div> <div className="flex items-center gap-2"> <RadioGroupItem id="radio-invalid-active" value="active" aria-invalid /> <Label htmlFor="radio-invalid-active">Active</Label> </div> <div className="flex items-center gap-2"> <RadioGroupItem id="radio-invalid-lockdown" value="lockdown" aria-invalid /> <Label htmlFor="radio-invalid-lockdown">Lockdown</Label> </div> </RadioGroup> <p className="font-mono text-xs text-destructive-spark"> SEC // select an operating posture to continue. </p> </div> );}Accessibility
- Built on Radix
RadioGroup.Root/Item, givingrole='radiogroup'with roving tabindex over the items. - Arrow keys move focus and select within the group; only the checked item is in the tab order.
- Selection is reflected via
data-state='checked', which fills the item signal and shows the core dot. aria-invalid='true'on an item flips it to the destructive keyline (border-destructive,ring-destructive).- Items render no text — give each
RadioGroupItemanidand pair aLabelviahtmlForfor the name and touch target. - Focus shows the 2px signal ring (
focus-visible:ring-2 ring-ring ring-offset-2); disabled items areopacity-60and skipped.
Guidelines
Do
- Pair each
RadioGroupItemwith aLabelso the label text is part of the hit area. - Use a radio group for one-of-many selection where every option should stay visible.
- Set a sensible
defaultValue(or controlledvalue) so the group is never empty on first paint.
Don't
- Don't use a radio group for independent on/off toggles — that's
Checkbox. - Don't offer a single radio with no alternative; if there's one option, use a checkbox.
- Don't rely on color alone for the selected state — the core dot and label carry it too.