Github

Radio Group

A set of checkable buttons—known as radio buttons—where no more than one of the buttons can be checked at a time.

import { Label } from "@/components/ui/label";
import {
	RadioGroup,
	RadioGroupItem,
} from "@/components/ui/radio-group";

export function RadioGroupDemo() {
	return (
		<RadioGroup defaultValue="comfortable" className="w-fit">
			<div className="flex items-center gap-3">
				<RadioGroupItem value="default" id="radio-demo-r1" />
				<Label htmlFor="radio-demo-r1">Default</Label>
			</div>
			<div className="flex items-center gap-3">
				<RadioGroupItem value="comfortable" id="radio-demo-r2" />
				<Label htmlFor="radio-demo-r2">Comfortable</Label>
			</div>
			<div className="flex items-center gap-3">
				<RadioGroupItem value="compact" id="radio-demo-r3" />
				<Label htmlFor="radio-demo-r3">Compact</Label>
			</div>
		</RadioGroup>
	);
}

Installation

pnpm dlx shadcn@latest add https://herocn.dev/r/radio-group.json

Usage

import { Label } from "@/components/ui/label"
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
<RadioGroup defaultValue="option-one">
  <div className="flex items-center gap-3">
    <RadioGroupItem value="option-one" id="option-one" />
    <Label htmlFor="option-one">Option One</Label>
  </div>
  <div className="flex items-center gap-3">
    <RadioGroupItem value="option-two" id="option-two" />
    <Label htmlFor="option-two">Option Two</Label>
  </div>
</RadioGroup>

Composition

Use the following composition to build a RadioGroup:

RadioGroup
├── RadioGroupItem
└── RadioGroupItem

Pair options with Label or Field (FieldLabel, FieldDescription) for accessible names and richer layouts.

Value

Use defaultValue for an uncontrolled group, or value and onValueChange for a controlled selection.

"use client"

import * as React from "react"
import { Field, FieldLabel } from "@/components/ui/field"
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"

export function Example() {
  const [plan, setPlan] = React.useState("monthly")

  return (
    <RadioGroup value={plan} onValueChange={setPlan} className="w-fit">
      <Field orientation="horizontal">
        <RadioGroupItem value="monthly" id="plan-monthly-ex" />
        <FieldLabel htmlFor="plan-monthly-ex" className="font-normal">
          Monthly
        </FieldLabel>
      </Field>
      <Field orientation="horizontal">
        <RadioGroupItem value="yearly" id="plan-yearly-ex" />
        <FieldLabel htmlFor="plan-yearly-ex" className="font-normal">
          Yearly
        </FieldLabel>
      </Field>
    </RadioGroup>
  )
}

Examples

Variants

Use the variant prop on RadioGroup for a softer secondary style on tinted panels (see In surface).

import { Field, FieldGroup, FieldLabel } from "@/components/ui/field";
import {
	RadioGroup,
	RadioGroupItem,
} from "@/components/ui/radio-group";

export function RadioGroupVariants() {
	return (
		<FieldGroup className="mx-auto w-full max-w-xs gap-4">
			<RadioGroup defaultValue="one" className="w-fit">
				<Field orientation="horizontal">
					<RadioGroupItem value="one" id="radio-var-def-1" />
					<FieldLabel htmlFor="radio-var-def-1" className="font-normal">
						Default
					</FieldLabel>
				</Field>
				<Field orientation="horizontal">
					<RadioGroupItem value="two" id="radio-var-def-2" />
					<FieldLabel htmlFor="radio-var-def-2" className="font-normal">
						Style
					</FieldLabel>
				</Field>
			</RadioGroup>
			<RadioGroup defaultValue="one" variant="secondary" className="w-fit">
				<Field orientation="horizontal">
					<RadioGroupItem value="one" id="radio-var-sec-1" />
					<FieldLabel htmlFor="radio-var-sec-1" className="font-normal">
						Secondary
					</FieldLabel>
				</Field>
				<Field orientation="horizontal">
					<RadioGroupItem value="two" id="radio-var-sec-2" />
					<FieldLabel htmlFor="radio-var-sec-2" className="font-normal">
						Style
					</FieldLabel>
				</Field>
			</RadioGroup>
		</FieldGroup>
	);
}

Description

Use Field, FieldContent, and FieldDescription for helper text beside each option.

Standard spacing for most use cases.

More space between elements.

Minimal spacing for dense layouts.

import {
	Field,
	FieldContent,
	FieldDescription,
	FieldLabel,
} from "@/components/ui/field";
import {
	RadioGroup,
	RadioGroupItem,
} from "@/components/ui/radio-group";

export function RadioGroupDescription() {
	return (
		<RadioGroup defaultValue="comfortable" className="w-fit">
			<Field orientation="horizontal">
				<RadioGroupItem value="default" id="radio-desc-r1" />
				<FieldContent>
					<FieldLabel htmlFor="radio-desc-r1">Default</FieldLabel>
					<FieldDescription>
						Standard spacing for most use cases.
					</FieldDescription>
				</FieldContent>
			</Field>
			<Field orientation="horizontal">
				<RadioGroupItem value="comfortable" id="radio-desc-r2" />
				<FieldContent>
					<FieldLabel htmlFor="radio-desc-r2">Comfortable</FieldLabel>
					<FieldDescription>More space between elements.</FieldDescription>
				</FieldContent>
			</Field>
			<Field orientation="horizontal">
				<RadioGroupItem value="compact" id="radio-desc-r3" />
				<FieldContent>
					<FieldLabel htmlFor="radio-desc-r3">Compact</FieldLabel>
					<FieldDescription>
						Minimal spacing for dense layouts.
					</FieldDescription>
				</FieldContent>
			</Field>
		</RadioGroup>
	);
}

Choice card

Use FieldLabel to wrap the entire Field for a clickable card-style selection.

Compute Environment

Select the compute environment for your cluster.

import {
	Field,
	FieldContent,
	FieldDescription,
	FieldGroup,
	FieldLabel,
	FieldLegend,
	FieldSet,
	FieldTitle,
} from "@/components/ui/field";
import {
	RadioGroup,
	RadioGroupItem,
} from "@/components/ui/radio-group";

export function FieldChoiceCard() {
	return (
		<FieldGroup className="w-full max-w-xs">
			<FieldSet>
				<FieldLegend variant="label">Compute Environment</FieldLegend>
				<FieldDescription>
					Select the compute environment for your cluster.
				</FieldDescription>
				<RadioGroup variant="secondary" defaultValue="kubernetes">
					<FieldLabel htmlFor="kubernetes-r2h">
						<Field orientation="horizontal">
							<FieldContent>
								<FieldTitle>Kubernetes</FieldTitle>
								<FieldDescription>
									Run GPU workloads on a K8s cluster.
								</FieldDescription>
							</FieldContent>
							<RadioGroupItem value="kubernetes" id="kubernetes-r2h" />
						</Field>
					</FieldLabel>
					<FieldLabel htmlFor="vm-z4k">
						<Field orientation="horizontal">
							<FieldContent>
								<FieldTitle>Virtual Machine</FieldTitle>
								<FieldDescription>
									Access a cluster to run GPU workloads.
								</FieldDescription>
							</FieldContent>
							<RadioGroupItem value="vm" id="vm-z4k" />
						</Field>
					</FieldLabel>
				</RadioGroup>
			</FieldSet>
		</FieldGroup>
	);
}

Fieldset

Use FieldSet and FieldLegend to group radio items with a label and description.

Subscription Plan

Yearly and lifetime plans offer significant savings.

import {
	Field,
	FieldDescription,
	FieldLabel,
	FieldLegend,
	FieldSet,
} from "@/components/ui/field";
import {
	RadioGroup,
	RadioGroupItem,
} from "@/components/ui/radio-group";

export function FieldRadio() {
	return (
		<FieldSet className="w-full max-w-xs">
			<FieldLegend variant="label">Subscription Plan</FieldLegend>
			<FieldDescription>
				Yearly and lifetime plans offer significant savings.
			</FieldDescription>
			<RadioGroup defaultValue="monthly">
				<Field orientation="horizontal">
					<RadioGroupItem value="monthly" id="plan-monthly" />
					<FieldLabel htmlFor="plan-monthly" className="font-normal">
						Monthly ($9.99/month)
					</FieldLabel>
				</Field>
				<Field orientation="horizontal">
					<RadioGroupItem value="yearly" id="plan-yearly" />
					<FieldLabel htmlFor="plan-yearly" className="font-normal">
						Yearly ($99.99/year)
					</FieldLabel>
				</Field>
				<Field orientation="horizontal">
					<RadioGroupItem value="lifetime" id="plan-lifetime" />
					<FieldLabel htmlFor="plan-lifetime" className="font-normal">
						Lifetime ($299.99)
					</FieldLabel>
				</Field>
			</RadioGroup>
		</FieldSet>
	);
}

Disabled

Pass disabled to a RadioGroupItem. Add data-disabled on the surrounding Field when the row should use disabled field styling.

import { Field, FieldLabel } from "@/components/ui/field";
import {
	RadioGroup,
	RadioGroupItem,
} from "@/components/ui/radio-group";

export function RadioGroupDisabled() {
	return (
		<RadioGroup defaultValue="option2" className="w-fit">
			<Field orientation="horizontal" data-disabled>
				<RadioGroupItem value="option1" id="radio-dis-1" disabled />
				<FieldLabel htmlFor="radio-dis-1" className="font-normal">
					Disabled
				</FieldLabel>
			</Field>
			<Field orientation="horizontal">
				<RadioGroupItem value="option2" id="radio-dis-2" />
				<FieldLabel htmlFor="radio-dis-2" className="font-normal">
					Option 2
				</FieldLabel>
			</Field>
			<Field orientation="horizontal">
				<RadioGroupItem value="option3" id="radio-dis-3" />
				<FieldLabel htmlFor="radio-dis-3" className="font-normal">
					Option 3
				</FieldLabel>
			</Field>
		</RadioGroup>
	);
}

Invalid

Set aria-invalid on RadioGroupItem and data-invalid on the Field wrapper to show invalid styles.

Notification Preferences

Choose how you want to receive notifications.

import {
	Field,
	FieldDescription,
	FieldLabel,
	FieldLegend,
	FieldSet,
} from "@/components/ui/field";
import {
	RadioGroup,
	RadioGroupItem,
} from "@/components/ui/radio-group";

export function RadioGroupInvalid() {
	return (
		<FieldSet className="w-full max-w-xs">
			<FieldLegend variant="label">Notification Preferences</FieldLegend>
			<FieldDescription>
				Choose how you want to receive notifications.
			</FieldDescription>
			<RadioGroup defaultValue="email">
				<Field orientation="horizontal" data-invalid>
					<RadioGroupItem value="email" id="radio-inv-email" aria-invalid />
					<FieldLabel htmlFor="radio-inv-email" className="font-normal">
						Email only
					</FieldLabel>
				</Field>
				<Field orientation="horizontal" data-invalid>
					<RadioGroupItem value="sms" id="radio-inv-sms" aria-invalid />
					<FieldLabel htmlFor="radio-inv-sms" className="font-normal">
						SMS only
					</FieldLabel>
				</Field>
				<Field orientation="horizontal" data-invalid>
					<RadioGroupItem value="both" id="radio-inv-both" aria-invalid />
					<FieldLabel htmlFor="radio-inv-both" className="font-normal">
						Both Email & SMS
					</FieldLabel>
				</Field>
			</RadioGroup>
		</FieldSet>
	);
}

In surface

Use variant="secondary" when placing a radio group inside a Surface or card so controls match the panel background.

Standard spacing for most use cases.

More space between elements.

Minimal spacing for dense layouts.

import {
	Field,
	FieldContent,
	FieldDescription,
	FieldLabel,
} from "@/components/ui/field";
import {
	RadioGroup,
	RadioGroupItem,
} from "@/components/ui/radio-group";
import { Surface } from "@/components/ui/surface";

export function RadioGroupInSurface() {
	return (
		<Surface className="flex w-full max-w-sm flex-col gap-4 rounded-3xl p-6">
			<RadioGroup
				defaultValue="comfortable"
				variant="secondary"
				className="w-fit"
			>
				<Field orientation="horizontal">
					<RadioGroupItem value="default" id="radio-surf-r1" />
					<FieldContent>
						<FieldLabel htmlFor="radio-surf-r1">Default</FieldLabel>
						<FieldDescription>
							Standard spacing for most use cases.
						</FieldDescription>
					</FieldContent>
				</Field>
				<Field orientation="horizontal">
					<RadioGroupItem value="comfortable" id="radio-surf-r2" />
					<FieldContent>
						<FieldLabel htmlFor="radio-surf-r2">Comfortable</FieldLabel>
						<FieldDescription>More space between elements.</FieldDescription>
					</FieldContent>
				</Field>
				<Field orientation="horizontal">
					<RadioGroupItem value="compact" id="radio-surf-r3" />
					<FieldContent>
						<FieldLabel htmlFor="radio-surf-r3">Compact</FieldLabel>
						<FieldDescription>
							Minimal spacing for dense layouts.
						</FieldDescription>
					</FieldContent>
				</Field>
			</RadioGroup>
		</Surface>
	);
}

RTL

Radio layout follows inline direction; use the same Field patterns as in LTR. See the RTL guide for app-wide configuration.

تباعد قياسي لمعظم حالات الاستخدام.

مساحة أكبر بين العناصر.

تباعد أدنى للتخطيطات الكثيفة.

"use client";

import {
	type Translations,
	useTranslation,
} from "@/components/language-selector";
import {
	Field,
	FieldContent,
	FieldDescription,
	FieldGroup,
	FieldLabel,
} from "@/components/ui/field";
import {
	RadioGroup,
	RadioGroupItem,
} from "@/components/ui/radio-group";

const translations: Translations = {
	en: {
		dir: "ltr",
		values: {
			default: "Default",
			defaultDescription: "Standard spacing for most use cases.",
			comfortable: "Comfortable",
			comfortableDescription: "More space between elements.",
			compact: "Compact",
			compactDescription: "Minimal spacing for dense layouts.",
		},
	},
	ar: {
		dir: "rtl",
		values: {
			default: "افتراضي",
			defaultDescription: "تباعد قياسي لمعظم حالات الاستخدام.",
			comfortable: "مريح",
			comfortableDescription: "مساحة أكبر بين العناصر.",
			compact: "مضغوط",
			compactDescription: "تباعد أدنى للتخطيطات الكثيفة.",
		},
	},
	he: {
		dir: "rtl",
		values: {
			default: "ברירת מחדל",
			defaultDescription: "ריווח סטנדרטי לרוב מקרי השימוש.",
			comfortable: "נוח",
			comfortableDescription: "יותר מקום בין האלמנטים.",
			compact: "קומפקטי",
			compactDescription: "ריווח מינימלי לפריסות צפופות.",
		},
	},
};

export function RadioGroupRtl() {
	const { language, dir, t } = useTranslation(translations, "ar");

	return (
		<FieldGroup lang={language} dir={dir} className="w-full">
			<RadioGroup defaultValue="comfortable" className="w-fit">
				<Field orientation="horizontal">
					<RadioGroupItem value="default" id="radio-rtl-r1" />
					<FieldContent>
						<FieldLabel htmlFor="radio-rtl-r1">{t.default}</FieldLabel>
						<FieldDescription>{t.defaultDescription}</FieldDescription>
					</FieldContent>
				</Field>
				<Field orientation="horizontal">
					<RadioGroupItem value="comfortable" id="radio-rtl-r2" />
					<FieldContent>
						<FieldLabel htmlFor="radio-rtl-r2">{t.comfortable}</FieldLabel>
						<FieldDescription>{t.comfortableDescription}</FieldDescription>
					</FieldContent>
				</Field>
				<Field orientation="horizontal">
					<RadioGroupItem value="compact" id="radio-rtl-r3" />
					<FieldContent>
						<FieldLabel htmlFor="radio-rtl-r3">{t.compact}</FieldLabel>
						<FieldDescription>{t.compactDescription}</FieldDescription>
					</FieldContent>
				</Field>
			</RadioGroup>
		</FieldGroup>
	);
}

API Reference

RadioGroup

The RadioGroup component forwards props to @base-ui/react RadioGroup and supports the following additional prop:

PropTypeDefault

RadioGroupItem

RadioGroupItem forwards props to the Base UI Radio root. For the full prop surface, see the Base UI Radio Group API.