Thrive Design System

Components

Radio

A control that allows users to select exactly one option from a set of mutually exclusive choices

Installation

npm install @thrivecart/ui
yarn add @thrivecart/ui
pnpm add @thrivecart/ui
bun add @thrivecart/ui

Usage

Import the Radio components from the package:

import { RadioGroup, Radio, Label } from '@thrivecart/ui';

Examples

Basic

Simple Radio Group

Basic radio group with labels

<RadioGroup defaultValue="option-1" className="space-y-2">
<div className="flex items-center space-x-2">
  <Radio value="option-1" id="option-1" />
  <Label htmlFor="option-1" className="font-normal">Option One</Label>
</div>
<div className="flex items-center space-x-2">
  <Radio value="option-2" id="option-2" />
  <Label htmlFor="option-2" className="font-normal">Option Two</Label>
</div>
<div className="flex items-center space-x-2">
  <Radio value="option-3" id="option-3" />
  <Label htmlFor="option-3" className="font-normal">Option Three</Label>
</div>
</RadioGroup>

Controlled

Controlled State

Radio group with controlled value

function ControlledRadioGroup() {
const [value, setValue] = React.useState('comfortable');

return (
  <div className="space-y-4">
    <RadioGroup value={value} onValueChange={setValue} className="space-y-2">
      <div className="flex items-center space-x-2">
        <Radio value="comfortable" id="comfortable" />
        <Label htmlFor="comfortable" className="font-normal">Comfortable</Label>
      </div>
      <div className="flex items-center space-x-2">
        <Radio value="compact" id="compact" />
        <Label htmlFor="compact" className="font-normal">Compact</Label>
      </div>
      <div className="flex items-center space-x-2">
        <Radio value="dense" id="dense" />
        <Label htmlFor="dense" className="font-normal">Dense</Label>
      </div>
    </RadioGroup>
    <p className="text-sm text-ink-light">
      Selected: <strong>{value}</strong>
    </p>
  </div>
);
}

With Descriptions

Options with Descriptions

Radio buttons with additional context below each label

Basic features for individuals getting started

Advanced features for professionals and small teams

<RadioGroup defaultValue="free" className="space-y-4">
<div className="flex items-start space-x-3">
  <Radio value="free" id="free" className="mt-0.5" />
  <div>
    <Label htmlFor="free" className="font-medium">Free Plan</Label>
    <p className="text-sm text-ink-light">Basic features for individuals getting started</p>
  </div>
</div>
<div className="flex items-start space-x-3">
  <Radio value="pro" id="pro" className="mt-0.5" />
  <div>
    <Label htmlFor="pro" className="font-medium">Pro Plan</Label>
    <p className="text-sm text-ink-light">Advanced features for professionals and small teams</p>
  </div>
</div>
</RadioGroup>

Horizontal Layout

Inline Radio Buttons

Radio group in horizontal layout

<RadioGroup defaultValue="left" className="flex gap-4">
<div className="flex items-center space-x-2">
  <Radio value="left" id="left" />
  <Label htmlFor="left" className="font-normal">Left</Label>
</div>
<div className="flex items-center space-x-2">
  <Radio value="center" id="center" />
  <Label htmlFor="center" className="font-normal">Center</Label>
</div>
<div className="flex items-center space-x-2">
  <Radio value="right" id="right" />
  <Label htmlFor="right" className="font-normal">Right</Label>
</div>
</RadioGroup>

Disabled State

Disabled Options

Some or all options disabled

<RadioGroup defaultValue="option-1" className="space-y-2">
<div className="flex items-center space-x-2">
  <Radio value="option-1" id="option-1" />
  <Label htmlFor="option-1" className="font-normal">Enabled option</Label>
</div>
<div className="flex items-center space-x-2">
  <Radio value="option-2" id="option-2" disabled />
  <Label htmlFor="option-2" className="font-normal">Disabled option</Label>
</div>
<div className="flex items-center space-x-2">
  <Radio value="option-3" id="option-3" />
  <Label htmlFor="option-3" className="font-normal">Another enabled option</Label>
</div>
</RadioGroup>

In Form Context

Form with Radio Group

Radio group in a complete form

function FormExample() {
const [formData, setFormData] = React.useState({
  frequency: 'daily',
  format: 'html',
});

return (
  <form className="w-80 p-6 border border-border rounded-lg space-y-6">
    <h3 className="text-lg font-semibold">Email Preferences</h3>

    <div className="space-y-3">
      <Label className="text-base font-medium">Email Frequency</Label>
      <RadioGroup
        value={formData.frequency}
        onValueChange={(value) => setFormData(prev => ({ ...prev, frequency: value }))}
        className="space-y-2"
      >
        <div className="flex items-center space-x-2">
          <Radio value="daily" id="daily" />
          <Label htmlFor="daily" className="font-normal">Daily digest</Label>
        </div>
        <div className="flex items-center space-x-2">
          <Radio value="weekly" id="weekly" />
          <Label htmlFor="weekly" className="font-normal">Weekly summary</Label>
        </div>
        <div className="flex items-center space-x-2">
          <Radio value="monthly" id="monthly" />
          <Label htmlFor="monthly" className="font-normal">Monthly newsletter</Label>
        </div>
      </RadioGroup>
    </div>

    <div className="space-y-3">
      <Label className="text-base font-medium">Email Format</Label>
      <RadioGroup
        value={formData.format}
        onValueChange={(value) => setFormData(prev => ({ ...prev, format: value }))}
        className="flex gap-4"
      >
        <div className="flex items-center space-x-2">
          <Radio value="html" id="html" />
          <Label htmlFor="html" className="font-normal">HTML</Label>
        </div>
        <div className="flex items-center space-x-2">
          <Radio value="text" id="text" />
          <Label htmlFor="text" className="font-normal">Plain text</Label>
        </div>
      </RadioGroup>
    </div>

    <Button type="submit" className="w-full">Save Preferences</Button>
  </form>
);
}

Radio Card

RadioCard provides a card-style radio selection interface with rich content including icons, labels, descriptions, and addons.

Basic Radio Card

Card Selection

Rich card-style radio options

Perfect for individuals and small projects

For growing teams with advanced needs

<RadioCard defaultValue="starter">
<RadioCardItem
  value="starter"
  label="Starter Plan"
  description="Perfect for individuals and small projects"
/>
<RadioCardItem
  value="pro"
  label="Pro Plan"
  description="For growing teams with advanced needs"
/>
<RadioCardItem
  value="enterprise"
  label="Enterprise"
  description="Custom solutions for large organizations"
/>
</RadioCard>

With Icons

Icon Cards

Radio cards with visual icons

Send via email campaign

Send via text message

<RadioCard defaultValue="email">
<RadioCardItem
  value="email"
  icon={<MdEmail className="size-6 text-primary" />}
  label="Email"
  description="Send via email campaign"
/>
<RadioCardItem
  value="sms"
  icon={<MdSms className="size-6 text-primary" />}
  label="SMS"
  description="Send via text message"
/>
<RadioCardItem
  value="push"
  icon={<MdNotifications className="size-6 text-primary" />}
  label="Push Notification"
  description="Send via mobile app"
/>
</RadioCard>

With Addon Content

Cards with Addon

Additional content like pricing or actions

Pay month-to-month, cancel anytime

$29/mo

Save 20% with annual payment

$279/yr
<RadioCard defaultValue="monthly">
<RadioCardItem
  value="monthly"
  label="Monthly Billing"
  description="Pay month-to-month, cancel anytime"
  addon={<span className="text-lg font-bold">$29/mo</span>}
/>
<RadioCardItem
  value="yearly"
  label="Yearly Billing"
  description="Save 20% with annual payment"
  addon={
    <div className="flex items-center gap-2">
      <span className="text-lg font-bold">$279/yr</span>
      <Badge variant="success" size="sm">Save $69</Badge>
    </div>
  }
/>
</RadioCard>

Best Practices

Use for mutually exclusive choices

Radio buttons are ideal when users must select exactly one option from a set.

Keep options visible

Show all options upfront so users can compare before choosing. Use Select for long lists.

Set a sensible default

Pre-select the most common or recommended option when appropriate.

Use descriptive labels

Labels should clearly indicate what each option does or represents.

Don't use for multiple selections

If users can select multiple options, use Checkbox instead.

Don't use for more than 5-7 options

For longer lists, consider using a Select dropdown instead.

Props

RadioGroup

PropTypeDefaultDescription
valuestring-Controlled value
defaultValuestring-Default value
onValueChange(value: string) => void-Change handler
disabledbooleanfalseDisable all items
requiredbooleanfalseMark as required
namestring-Form field name
orientation'horizontal' | 'vertical''vertical'Layout direction
classNamestring-Additional className

Radio

PropTypeDefaultDescription
valuestring-Item value (required)
disabledbooleanfalseDisable this item
idstring-Element ID for label association
classNamestring-Additional className

RadioCard

Extends RadioGroup with the same props.

RadioCardItem

PropTypeDefaultDescription
valuestring-Item value (required)
labelReactNode-Card title/label
descriptionReactNode-Description text below label
iconReactElement-Icon element to display
addonReactNode-Additional content (e.g., pricing)
indicatorReactNode | null-Custom indicator element
indicatorPlacement'start' | 'end' | 'inside'-Position of indicator
disabledbooleanfalseDisable this item
classNamestring-Additional className

Accessibility

Keyboard Navigation

Radio groups support keyboard navigation. Use arrow keys to move between options, and Space to select.

  • Built on Radix UI RadioGroup with full ARIA support
  • Uses proper role="radiogroup" and role="radio" attributes
  • Arrow keys navigate between options in the group
  • Space key selects the focused option
  • Focus is automatically managed within the group
  • Required state is announced by assistive technology