RadioGroup
Client leafA set of mutually exclusive options.
Live preview
Rendered from the installed VEXA source — the same files you receive.
Installation
Adds the component source to your project. Registry dependencies are pulled in automatically.
npx shadcn@latest add https://vexa.valfiguer.com/r/radio-group.jsonInstalls to components/vexa/radio-group.tsx
npm dependencies
- @radix-ui/react-radio-group
Registry dependencies
- @vexa/utils
Other VEXA items this component needs. Install them first — they are resolved automatically by the CLI.
Source
Copy this into its target path, or install it with the command above.
"use client";
import type * as React from "react";
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group";
import { cn } from "@/lib/utils";
/**
* RadioGroup — a set of mutually exclusive options.
*
* Client leaf. Radix provides the `radiogroup`/`radio` roles and the APG roving
* focus model: Arrow keys move between and select options, Tab moves in and out
* of the group. Give the group an accessible name with `aria-label` or
* `aria-labelledby`, and associate each item with a `<Label htmlFor>`.
*/
export function RadioGroup({
className,
...props
}: React.ComponentProps<typeof RadioGroupPrimitive.Root>) {
return (
<RadioGroupPrimitive.Root
data-slot="radio-group"
className={cn("grid gap-2.5", className)}
{...props}
/>
);
}
export function RadioGroupItem({
className,
...props
}: React.ComponentProps<typeof RadioGroupPrimitive.Item>) {
return (
<RadioGroupPrimitive.Item
data-slot="radio-group-item"
className={cn(
"aspect-square size-5 shrink-0 rounded-full border border-vexa-input bg-vexa-surface-raised shadow-vexa-sm",
"transition-[color,border-color,box-shadow] duration-[var(--vexa-duration-fast)] ease-[var(--vexa-ease-standard)]",
"outline-none focus-visible:ring-2 focus-visible:ring-vexa-ring focus-visible:ring-offset-2 focus-visible:ring-offset-vexa-background",
"disabled:cursor-not-allowed disabled:opacity-50",
"data-[state=checked]:border-vexa-primary",
"aria-[invalid=true]:border-vexa-danger",
className,
)}
{...props}
>
<RadioGroupPrimitive.Indicator
data-slot="radio-group-indicator"
className="flex items-center justify-center"
>
<span className="size-2.5 rounded-full bg-vexa-primary" />
</RadioGroupPrimitive.Indicator>
</RadioGroupPrimitive.Item>
);
}
Registry JSONr/radio-group.json
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "radio-group",
"type": "registry:ui",
"title": "RadioGroup",
"description": "A set of mutually exclusive options.",
"dependencies": [
"@radix-ui/react-radio-group"
],
"registryDependencies": [
"@vexa/utils"
],
"files": [
{
"path": "registry/default/ui/radio-group.tsx",
"type": "registry:ui",
"target": "components/vexa/radio-group.tsx",
"content": "\"use client\";\n\nimport type * as React from \"react\";\nimport * as RadioGroupPrimitive from \"@radix-ui/react-radio-group\";\n\nimport { cn } from \"@/lib/utils\";\n\n/**\n * RadioGroup — a set of mutually exclusive options.\n *\n * Client leaf. Radix provides the `radiogroup`/`radio` roles and the APG roving\n * focus model: Arrow keys move between and select options, Tab moves in and out\n * of the group. Give the group an accessible name with `aria-label` or\n * `aria-labelledby`, and associate each item with a `<Label htmlFor>`.\n */\nexport function RadioGroup({\n className,\n ...props\n}: React.ComponentProps<typeof RadioGroupPrimitive.Root>) {\n return (\n <RadioGroupPrimitive.Root\n data-slot=\"radio-group\"\n className={cn(\"grid gap-2.5\", className)}\n {...props}\n />\n );\n}\n\nexport function RadioGroupItem({\n className,\n ...props\n}: React.ComponentProps<typeof RadioGroupPrimitive.Item>) {\n return (\n <RadioGroupPrimitive.Item\n data-slot=\"radio-group-item\"\n className={cn(\n \"aspect-square size-5 shrink-0 rounded-full border border-vexa-input bg-vexa-surface-raised shadow-vexa-sm\",\n \"transition-[color,border-color,box-shadow] duration-[var(--vexa-duration-fast)] ease-[var(--vexa-ease-standard)]\",\n \"outline-none focus-visible:ring-2 focus-visible:ring-vexa-ring focus-visible:ring-offset-2 focus-visible:ring-offset-vexa-background\",\n \"disabled:cursor-not-allowed disabled:opacity-50\",\n \"data-[state=checked]:border-vexa-primary\",\n \"aria-[invalid=true]:border-vexa-danger\",\n className,\n )}\n {...props}\n >\n <RadioGroupPrimitive.Indicator\n data-slot=\"radio-group-indicator\"\n className=\"flex items-center justify-center\"\n >\n <span className=\"size-2.5 rounded-full bg-vexa-primary\" />\n </RadioGroupPrimitive.Indicator>\n </RadioGroupPrimitive.Item>\n );\n}\n"
}
],
"meta": {
"frameworks": [
"react",
"next"
],
"rsc": "client",
"exports": [
"RadioGroup",
"RadioGroupItem"
],
"cssVars": [
"--vexa-input",
"--vexa-surface-raised",
"--vexa-primary",
"--vexa-ring",
"--vexa-danger",
"--vexa-background"
],
"a11y": "APG radiogroup model: Arrow keys move and select, Tab enters/exits the group. Give the group a name via aria-label/aria-labelledby and label each item.",
"example": "<RadioGroup defaultValue=\"a\" aria-label=\"Plan\"><div className=\"flex items-center gap-2\"><RadioGroupItem value=\"a\" id=\"a\" /><Label htmlFor=\"a\">Starter</Label></div></RadioGroup>",
"tests": "planned — see docs/README.md#roadmap"
}
}Details
Accessibility
APG radiogroup model: Arrow keys move and select, Tab enters/exits the group. Give the group a name via aria-label/aria-labelledby and label each item.
Usage
Choose exactly one option from a small, visible set.
Anatomy
- RadioGroup
- The group; owns the selected value.
- RadioGroupItem
- One option; pair each with a Label.
Accessibility
APG radiogroup: role=radiogroup / role=radio. Give the group a name via `aria-label` / `aria-labelledby` and label each item.
Keyboard
| Key | Action |
|---|---|
| Arrow keys | Move to and select the previous/next option |
| Tab | Enter or leave the group (roving tabindex) |
Focus: Roving focus on the checked (or first) item; visible ring.
Guidelines
Do
- Name the group.
- Label every item.
- Set a sensible default when one exists.
Don't
- Don't use radios for multi-select — use Checkbox.
- Don't place a Tab stop between each item.
- Don't leave the group unnamed.
Was this page helpful?
Stored locally in this browser. No account or server needed.