Radio Group

A radio group component built on Radix UI for mutually exclusive selections with keyboard navigation.

Usage

Basic Radio Group

import { RadioGroup, RadioGroupItem } from "@prisma-docs/eclipse";

export function BasicRadioGroup() {
  return (
    <RadioGroup defaultValue="option-one">
      <div className="flex items-center gap-2">
        <RadioGroupItem value="option-one" id="option-one" />
        <label htmlFor="option-one" className="text-sm font-medium">Option One</label>
      </div>
      <div className="flex items-center gap-2">
        <RadioGroupItem value="option-two" id="option-two" />
        <label htmlFor="option-two" className="text-sm font-medium">Option Two</label>
      </div>
    </RadioGroup>
  );
}

Live Example:

With Descriptions

import { RadioGroup, RadioGroupItem } from "@prisma-docs/eclipse";

export function RadioGroupWithDescriptions() {
  return (
    <RadioGroup defaultValue="comfortable">
      <div className="flex items-center gap-2">
        <RadioGroupItem value="default" id="default" />
        <div className="flex flex-col gap-1">
          <label htmlFor="default" className="text-sm font-medium">Default</label>
          <span className="text-sm text-foreground-neutral-weak">The standard option for most users.</span>
        </div>
      </div>
      <div className="flex items-center gap-2">
        <RadioGroupItem value="comfortable" id="comfortable" />
        <div className="flex flex-col gap-1">
          <label htmlFor="comfortable" className="text-sm font-medium">Comfortable</label>
          <span className="text-sm text-foreground-neutral-weak">More spacing for better readability.</span>
        </div>
      </div>
      <div className="flex items-center gap-2">
        <RadioGroupItem value="compact" id="compact" />
        <div className="flex flex-col gap-1">
          <label htmlFor="compact" className="text-sm font-medium">Compact</label>
          <span className="text-sm text-foreground-neutral-weak">Minimal spacing to fit more content.</span>
        </div>
      </div>
    </RadioGroup>
  );
}

Live Example:

The standard option for most users.
More spacing for better readability.
Minimal spacing to fit more content.

With Field Component

import { RadioGroup, RadioGroupItem, Field, FieldLabel, FieldDescription } from "@prisma-docs/eclipse";

export function RadioGroupWithField() {
  return (
    <Field>
      <FieldLabel>Notification Frequency</FieldLabel>
      <FieldDescription>Choose how often you want to receive notifications.</FieldDescription>
      <RadioGroup defaultValue="daily">
        <div className="flex items-center gap-2">
          <RadioGroupItem value="realtime" id="realtime" />
          <label htmlFor="realtime" className="text-sm font-medium">Real-time</label>
        </div>
        <div className="flex items-center gap-2">
          <RadioGroupItem value="daily" id="daily" />
          <label htmlFor="daily" className="text-sm font-medium">Daily digest</label>
        </div>
        <div className="flex items-center gap-2">
          <RadioGroupItem value="weekly" id="weekly" />
          <label htmlFor="weekly" className="text-sm font-medium">Weekly summary</label>
        </div>
      </RadioGroup>
    </Field>
  );
}

Live Example:

Choose how often you want to receive notifications.

Controlled Radio Group

import { RadioGroup, RadioGroupItem } from "@prisma-docs/eclipse";
import { useState } from "react";

export function ControlledRadioGroup() {
  const [value, setValue] = useState("option-one");

  return (
    <div className="space-y-4">
      <RadioGroup value={value} onValueChange={setValue}>
        <div className="flex items-center gap-2">
          <RadioGroupItem value="option-one" id="controlled-one" />
          <label htmlFor="controlled-one" className="text-sm font-medium">Option One</label>
        </div>
        <div className="flex items-center gap-2">
          <RadioGroupItem value="option-two" id="controlled-two" />
          <label htmlFor="controlled-two" className="text-sm font-medium">Option Two</label>
        </div>
      </RadioGroup>
      <span className="text-sm text-foreground-neutral-weak">
        Selected: {value}
      </span>
    </div>
  );
}

Live Example:

Disabled State

import { RadioGroup, RadioGroupItem } from "@prisma-docs/eclipse";

export function DisabledRadioGroup() {
  return (
    <RadioGroup defaultValue="option-one">
      <div className="flex items-center gap-2">
        <RadioGroupItem value="option-one" id="disabled-one" disabled />
        <label htmlFor="disabled-one" className="text-sm font-medium opacity-70 cursor-not-allowed">Disabled option (selected)</label>
      </div>
      <div className="flex items-center gap-2">
        <RadioGroupItem value="option-two" id="disabled-two" disabled />
        <label htmlFor="disabled-two" className="text-sm font-medium opacity-70 cursor-not-allowed">Disabled option</label>
      </div>
      <div className="flex items-center gap-2">
        <RadioGroupItem value="option-three" id="enabled-three" />
        <label htmlFor="enabled-three" className="text-sm font-medium">Enabled option</label>
      </div>
    </RadioGroup>
  );
}

Live Example:

Horizontal Layout

import { RadioGroup, RadioGroupItem } from "@prisma-docs/eclipse";

export function HorizontalRadioGroup() {
  return (
    <RadioGroup defaultValue="small" className="flex gap-4">
      <div className="flex items-center gap-2">
        <RadioGroupItem value="small" id="size-small" />
        <label htmlFor="size-small" className="text-sm font-medium">Small</label>
      </div>
      <div className="flex items-center gap-2">
        <RadioGroupItem value="medium" id="size-medium" />
        <label htmlFor="size-medium" className="text-sm font-medium">Medium</label>
      </div>
      <div className="flex items-center gap-2">
        <RadioGroupItem value="large" id="size-large" />
        <label htmlFor="size-large" className="text-sm font-medium">Large</label>
      </div>
    </RadioGroup>
  );
}

Live Example:

In Forms

import { RadioGroup, RadioGroupItem, Button } from "@prisma-docs/eclipse";

export function RadioGroupForm() {
  return (
    <form className="space-y-6 max-w-md">
      <div className="space-y-4">
        <h3 className="text-lg font-semibold">Shipping Method</h3>
        
        <RadioGroup defaultValue="standard" name="shipping">
          <div className="flex items-center justify-between p-4 border rounded-lg">
            <div className="flex items-center gap-3">
              <RadioGroupItem value="standard" id="standard-shipping" />
              <div className="flex flex-col gap-1">
                <label htmlFor="standard-shipping" className="text-sm font-medium">Standard Shipping</label>
                <span className="text-sm text-foreground-neutral-weak">5-7 business days</span>
              </div>
            </div>
            <span className="text-sm font-medium">Free</span>
          </div>
          
          <div className="flex items-center justify-between p-4 border rounded-lg">
            <div className="flex items-center gap-3">
              <RadioGroupItem value="express" id="express-shipping" />
              <div className="flex flex-col gap-1">
                <label htmlFor="express-shipping" className="text-sm font-medium">Express Shipping</label>
                <span className="text-sm text-foreground-neutral-weak">2-3 business days</span>
              </div>
            </div>
            <span className="text-sm font-medium">$15.00</span>
          </div>
          
          <div className="flex items-center justify-between p-4 border rounded-lg">
            <div className="flex items-center gap-3">
              <RadioGroupItem value="overnight" id="overnight-shipping" />
              <div className="flex flex-col gap-1">
                <label htmlFor="overnight-shipping" className="text-sm font-medium">Overnight Shipping</label>
                <span className="text-sm text-foreground-neutral-weak">Next business day</span>
              </div>
            </div>
            <span className="text-sm font-medium">$30.00</span>
          </div>
        </RadioGroup>
      </div>
      
      <Button type="submit">Continue to payment</Button>
    </form>
  );
}

Live Example:

Shipping Method

5-7 business days
Free
2-3 business days
$15.00
Next business day
$30.00

Component Props

RadioGroup

  • value - Controlled value (string, optional)
  • defaultValue - Default value for uncontrolled usage (string, optional)
  • onValueChange - Callback when value changes (function, optional)
  • disabled - Whether the group is disabled (boolean, default: false)
  • required - Whether selection is required (boolean, default: false)
  • name - Name for form submission (string, optional)
  • className - Additional CSS classes (string, optional)
  • All standard Radix UI RadioGroup Root props

RadioGroupItem

  • value - Value of this radio item (string, required)
  • disabled - Whether this item is disabled (boolean, default: false)
  • id - HTML id attribute (string, optional)
  • className - Additional CSS classes (string, optional)
  • All standard Radix UI RadioGroup Item props

Features

  • ✅ Built on Radix UI for robust accessibility
  • ✅ Keyboard navigation (Arrow keys, Tab, Space)
  • ✅ Controlled and uncontrolled modes
  • ✅ Disabled state for individual items or entire group
  • ✅ Focus visible indicators
  • ✅ Selected state with animated indicator
  • ✅ Works with forms and validation
  • ✅ Screen reader support
  • ✅ Flexible layout (vertical or horizontal)
  • ✅ Customizable with className
  • ✅ Fully typed with TypeScript

Best Practices

  • Always use labels with radio buttons for better UX
  • Use htmlFor on labels to match RadioGroupItem id
  • Group related options together logically
  • Provide descriptions for complex choices
  • Use controlled state when you need to react to changes
  • Set a sensible defaultValue for uncontrolled usage
  • Consider using Field components for forms
  • Test keyboard navigation thoroughly
  • Ensure labels are clickable
  • Use disabled state sparingly
  • Provide enough spacing between options

Common Use Cases

The RadioGroup component is perfect for:

  • Settings selection - Choose one option from several
  • Shipping methods - Delivery options
  • Payment methods - Credit card, PayPal, etc.
  • Plan selection - Subscription tiers
  • Preferences - Theme, language, etc.
  • Surveys - Single-choice questions
  • Filters - Sorting and filtering options
  • Size selection - Product sizes (S, M, L, XL)

Accessibility

The RadioGroup component follows accessibility best practices:

  • Uses Radix UI's accessible radio group primitive
  • Proper ARIA attributes (role="radiogroup", aria-checked)
  • Keyboard accessible (Arrow keys to navigate, Space to select)
  • Focus visible indicators
  • Screen reader support with proper labels
  • Disabled state prevents interaction
  • Works with form validation
  • Supports required attribute
  • Proper focus management within group
  • High contrast indicators

Keyboard Shortcuts

  • Arrow Up/Down - Navigate between radio items (vertical)
  • Arrow Left/Right - Navigate between radio items (horizontal)
  • Space - Select focused radio item
  • Tab - Move focus to next element outside group
  • Shift + Tab - Move focus to previous element

Styling

The RadioGroup component uses design tokens:

  • Border: border-primary
  • Selected indicator: fill-primary
  • Focus ring: ring-ring
  • Size: 16×16px (h-4 w-4)
  • Indicator size: 14×14px (h-3.5 w-3.5)
  • Border radius: rounded-full
  • Gap: gap-2 (8px between items)

Customize by passing className:

<RadioGroup className="flex gap-4">
  <RadioGroupItem className="border-2 border-blue-500" />
</RadioGroup>

Integration with Other Components

RadioGroup works well with:

  • Field - Form structure with labels and descriptions
  • FieldLabel - Group labels
  • FieldDescription - Helper text for the group
  • FieldError - Validation messages
  • Forms - Form submission and validation
  • Cards - Options within cards
  • Dialogs - Settings in modals

Form Integration

The RadioGroup component works seamlessly with forms:

<form onSubmit={handleSubmit}>
  <RadioGroup name="plan" defaultValue="pro" required>
    <RadioGroupItem value="free" id="free" />
    <RadioGroupItem value="pro" id="pro" />
  </RadioGroup>
  <button type="submit">Submit</button>
</form>

When selected, the form data will include the selected value with the specified name.

On this page