Radio Group

Choose one option from a related set.

A set of radio buttons where only one option can be selected.

tsx
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"export function Example() {  return (    <RadioGroup defaultValue="comfortable">      <label className="flex items-center gap-2.5 text-sm tracking-wide">        <RadioGroupItem value="default" id="r-basic-default" />        Default      </label>      <label className="flex items-center gap-2.5 text-sm tracking-wide">        <RadioGroupItem value="comfortable" id="r-basic-comfortable" />        Comfortable      </label>      <label className="flex items-center gap-2.5 text-sm tracking-wide">        <RadioGroupItem value="compact" id="r-basic-compact" />        Compact      </label>    </RadioGroup>  )}

Installation

npx @arctis-sh/@arctis-sh/ui@latest add radio-group

Usage

import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
<RadioGroup defaultValue="option-one">  <label className="flex items-center gap-2.5 text-sm tracking-wide">    <RadioGroupItem value="option-one" id="option-one" />    Option A  </label>  <label className="flex items-center gap-2.5 text-sm tracking-wide">    <RadioGroupItem value="option-two" id="option-two" />    Option B  </label></RadioGroup>

Composition

RadioGroup
├── RadioGroupItem
└── RadioGroupItem

Basic

Simple labeled options. Use defaultValue to set the initial uncontrolled value.

tsx
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"export function Example() {  return (    <RadioGroup defaultValue="comfortable">      <label className="flex items-center gap-2.5 text-sm tracking-wide">        <RadioGroupItem value="default" id="r-basic-default" />        Default      </label>      <label className="flex items-center gap-2.5 text-sm tracking-wide">        <RadioGroupItem value="comfortable" id="r-basic-comfortable" />        Comfortable      </label>      <label className="flex items-center gap-2.5 text-sm tracking-wide">        <RadioGroupItem value="compact" id="r-basic-compact" />        Compact      </label>    </RadioGroup>  )}

Description

Add a title and muted helper text when a short label is not enough.

tsx
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"export function Example() {  return (    <RadioGroup defaultValue="comfortable">      <label className="flex items-start gap-2.5">        <RadioGroupItem          value="default"          id="r-desc-default"          className="mt-0.5"        />        <span className="grid gap-1">          <span className="text-sm font-medium tracking-wide">Default</span>          <span className="text-sm text-muted-foreground">            Standard spacing for most use cases.          </span>        </span>      </label>      <label className="flex items-start gap-2.5">        <RadioGroupItem          value="comfortable"          id="r-desc-comfortable"          className="mt-0.5"        />        <span className="grid gap-1">          <span className="text-sm font-medium tracking-wide">            Comfortable          </span>          <span className="text-sm text-muted-foreground">            More space between elements.          </span>        </span>      </label>      <label className="flex items-start gap-2.5">        <RadioGroupItem          value="compact"          id="r-desc-compact"          className="mt-0.5"        />        <span className="grid gap-1">          <span className="text-sm font-medium tracking-wide">Compact</span>          <span className="text-sm text-muted-foreground">            Minimal spacing for dense layouts.          </span>        </span>      </label>    </RadioGroup>  )}

Card

Wrap each option in a Card for a larger choice surface. Control selection with value and onValueChange; the selected card uses a filled surface.

tsx
import * as React from "react"import { Card, CardContent } from "@/components/ui/card"import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"import { cn } from "@/lib/utils"export function Example() {  const [plan, setPlan] = React.useState("pro")  return (    <RadioGroup value={plan} onValueChange={setPlan} className="w-full gap-3">      <label className="block w-full cursor-pointer">        <Card className={cn("py-0", plan !== "plus" && "bg-transparent")}>          <CardContent className="flex items-start gap-2.5 px-2.5 py-2.5">            <RadioGroupItem value="plus" id="r-card-plus" className="mt-0.5" />            <span className="grid gap-1">              <span className="text-sm font-medium tracking-wide">Plus</span>              <span className="text-sm text-muted-foreground">                For individuals and small teams.              </span>            </span>          </CardContent>        </Card>      </label>      <label className="block w-full cursor-pointer">        <Card className={cn("py-0", plan !== "pro" && "bg-transparent")}>          <CardContent className="flex items-start gap-2.5 px-2.5 py-2.5">            <RadioGroupItem value="pro" id="r-card-pro" className="mt-0.5" />            <span className="grid gap-1">              <span className="text-sm font-medium tracking-wide">Pro</span>              <span className="text-sm text-muted-foreground">                For growing businesses.              </span>            </span>          </CardContent>        </Card>      </label>      <label className="block w-full cursor-pointer">        <Card          className={cn("py-0", plan !== "enterprise" && "bg-transparent")}        >          <CardContent className="flex items-start gap-2.5 px-2.5 py-2.5">            <RadioGroupItem              value="enterprise"              id="r-card-enterprise"              className="mt-0.5"            />            <span className="grid gap-1">              <span className="text-sm font-medium tracking-wide">                Enterprise              </span>              <span className="text-sm text-muted-foreground">                For large teams and enterprises.              </span>            </span>          </CardContent>        </Card>      </label>    </RadioGroup>  )}

Fieldset

Group radios in a native fieldset and legend with helper text.

Subscription PlanYearly and lifetime plans offer significant savings.
tsx
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"export function Example() {  return (    <fieldset className="m-0 grid w-full gap-3 border-0 p-0">      <legend className="float-left w-full p-0">        <span className="grid gap-1 pb-3">          <span className="text-sm font-medium tracking-wide text-foreground">            Subscription Plan          </span>          <span className="text-sm font-normal text-muted-foreground">            Yearly and lifetime plans offer significant savings.          </span>        </span>      </legend>      <RadioGroup defaultValue="yearly">        <label className="flex items-center gap-2.5 text-sm tracking-wide">          <RadioGroupItem value="monthly" id="r-fieldset-monthly" />          Monthly ($9.99/month)        </label>        <label className="flex items-center gap-2.5 text-sm tracking-wide">          <RadioGroupItem value="yearly" id="r-fieldset-yearly" />          Yearly ($99.99/year)        </label>        <label className="flex items-center gap-2.5 text-sm tracking-wide">          <RadioGroupItem value="lifetime" id="r-fieldset-lifetime" />          Lifetime ($299.99)        </label>      </RadioGroup>    </fieldset>  )}

Disabled

disabled on RadioGroup locks every item. Dim the labels so the list reads as inactive.

tsx
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"export function Example() {  return (    <RadioGroup defaultValue="option-one" disabled>      <label className="flex items-center gap-2.5 text-sm tracking-wide opacity-60">        <RadioGroupItem value="option-one" id="r-disabled-one" />        Option One      </label>      <label className="flex items-center gap-2.5 text-sm tracking-wide opacity-60">        <RadioGroupItem value="option-two" id="r-disabled-two" />        Option Two      </label>      <label className="flex items-center gap-2.5 text-sm tracking-wide opacity-60">        <RadioGroupItem value="option-three" id="r-disabled-three" />        Option Three      </label>    </RadioGroup>  )}

Invalid

Pass aria-invalid to the items for error styling, and use destructive text for their labels.

Notification preferences

Choose how you want to receive notifications.

tsx
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"export function Example() {  return (    <div className="grid w-full gap-3">      <div className="grid gap-1">        <p className="text-sm font-medium tracking-wide text-destructive">          Notification preferences        </p>        <p className="text-sm text-muted-foreground">          Choose how you want to receive notifications.        </p>      </div>      <RadioGroup defaultValue="email">        <label className="flex items-center gap-2.5 text-sm tracking-wide text-destructive">          <RadioGroupItem value="email" id="r-invalid-email" aria-invalid />          Email only        </label>        <label className="flex items-center gap-2.5 text-sm tracking-wide text-destructive">          <RadioGroupItem value="sms" id="r-invalid-sms" aria-invalid />          SMS only        </label>        <label className="flex items-center gap-2.5 text-sm tracking-wide text-destructive">          <RadioGroupItem value="both" id="r-invalid-both" aria-invalid />          Both Email & SMS        </label>      </RadioGroup>    </div>  )}

API Reference

RadioGroup

PropTypeDefault
valuestring
defaultValuestring""
onValueChange(value: string) => void
disabledboolean
orientation"vertical" | "horizontal""vertical"
classNamestring
<RadioGroup defaultValue="a">  <RadioGroupItem value="a" />  <RadioGroupItem value="b" /></RadioGroup>

RadioGroupItem

Must be used inside RadioGroup. Arrow keys move selection within the group. Pass aria-invalid for validation styling.

PropTypeDefault
valuestring
disabledboolean
classNamestring
<RadioGroupItem value="a" />