Radio Card
Radio cards combine a radio button with a card layout, allowing users to select exactly one option from a set with rich contextual information. Ideal for plan selection, payment methods, or any single-select scenario where visual hierarchy matters.
Basic Usage
Radio Card offers two APIs: a simple pre-composed component for common use cases, and an advanced compound component API for full control.
import { SimpleRadioCard, SimpleRadioCardGroup } from '@/components'
<SimpleRadioCardGroup value={value} onValueChange={setValue}>
<SimpleRadioCard
value="option1"
label="Enable notifications"
subtext="Receive updates about your account activity"
/>
<SimpleRadioCard
value="option2"
label="Disable notifications"
subtext="You won't receive any notifications"
/>
</SimpleRadioCardGroup>Variants
Radio cards support four visual variants to accommodate different content types.
Text variant (default)
Icon variant
Avatar variant
Card Provider variant
Text (Default)
Simple text-only card with label, subtext, and optional badge.
<RadioCardGroup>
<RadioCard
value="option"
label="Text option"
subtext="Simple text-only card"
badge="Default"
/>
</RadioCardGroup>Icon
Card with a circular icon container, great for integrations or feature toggles.
<RadioCardGroup>
<RadioCard
value="gitlab"
variant="icon"
label="GitLab Integration"
subtext="Connect your GitLab account"
icon={<GitLabIcon />}
/>
</RadioCardGroup>Avatar
Card with an avatar image, perfect for user selection or team members.
<RadioCardGroup>
<RadioCard
value="sofia"
variant="avatar"
label="Sofia Martinez"
subtext="Product Designer"
avatar={<img src="..." alt="Sofia" />}
/>
</RadioCardGroup>Card Provider
Card with a payment provider or brand icon.
<RadioCardGroup>
<RadioCard
value="applepay"
variant="cardProvider"
label="Apple Pay"
subtext="Pay with Apple Pay"
cardProvider={<ApplePayIcon />}
/>
</RadioCardGroup>States
Radio cards support multiple visual states to indicate selection, errors, and disabled states.
Default - Unselected
Default - Selected
Error
Disabled - Unselected
Disabled - Selected
Selected
When selected, the card displays a blue border to indicate selection.
<RadioCardGroup value="option">
<RadioCard value="option" label="Selected option" />
</RadioCardGroup>Error
Use the error state for validation errors or required fields.
<RadioCardGroup>
<RadioCard value="option" label="Required field" error />
</RadioCardGroup>Disabled
Disabled cards cannot be interacted with and display muted styling.
<RadioCardGroup>
<RadioCard value="option" label="Disabled option" disabled />
</RadioCardGroup>
<RadioCardGroup value="option">
<RadioCard value="option" label="Disabled selected" disabled />
</RadioCardGroup>Clickable Behavior
Control whether the entire card or just the radio button is interactive.
clickableCard=true (default)
clickableCard=false
Entire Card (Default)
By default, clicking anywhere on the card selects the radio.
<RadioCard
value="option"
label="Click anywhere"
clickableCard={true} // default
/>Radio Only
Set clickableCard={false} to make only the radio button interactive.
<RadioCard
value="option"
label="Click radio only"
clickableCard={false}
/>With Badge
Badges can highlight important information like recommendations or pricing.
<RadioCardGroup>
<RadioCard
value="premium"
label="Premium Plan"
subtext="Access all features"
badge="Recommended"
/>
<RadioCard
value="basic"
label="Basic Plan"
subtext="Essential features"
badge="Free"
/>
</RadioCardGroup>Controlled State
Manage selection with controlled state.
Selected: option1
const [selected, setSelected] = useState('option1')
<RadioCardGroup value={selected} onValueChange={setSelected}>
<RadioCard value="option1" label="Option 1" />
<RadioCard value="option2" label="Option 2" />
<RadioCard value="option3" label="Option 3" />
</RadioCardGroup>All States Overview
Advanced Compound Component API
For complex layouts or when you need full control over the card structure, use the compound component API.
Basic Compound Usage
import { RadioCard } from '@/components'
<RadioCard.Group value={value} onValueChange={setValue}>
<RadioCard.Item value="notifications">
<RadioCard.Leading>
<RadioCard.Content>
<RadioCard.TitleRow>
<RadioCard.Label>Enable notifications</RadioCard.Label>
</RadioCard.TitleRow>
<RadioCard.Description>
Receive updates about your account activity
</RadioCard.Description>
</RadioCard.Content>
</RadioCard.Leading>
<RadioCard.Indicator />
</RadioCard.Item>
</RadioCard.Group>With Icons and Badges
The compound API makes it easy to combine icons, badges, and custom content.
<RadioCard.Group value={value} onValueChange={setValue}>
<RadioCard.Item value="settings">
<RadioCard.Leading variant="icon">
<RadioCard.Icon>
<SettingsIcon />
</RadioCard.Icon>
<RadioCard.Content>
<RadioCard.TitleRow>
<RadioCard.Label>General Settings</RadioCard.Label>
<RadioCard.Badge>Default</RadioCard.Badge>
</RadioCard.TitleRow>
<RadioCard.Description>
Configure your general preferences
</RadioCard.Description>
</RadioCard.Content>
</RadioCard.Leading>
<RadioCard.Indicator />
</RadioCard.Item>
</RadioCard.Group>Custom Layout with Right Slot
Use RadioCard.RightSlot for custom content like pricing or actions.
<RadioCard.Group value={value} onValueChange={setValue}>
<RadioCard.Item value="pro">
<RadioCard.Leading>
<RadioCard.Content>
<RadioCard.TitleRow>
<RadioCard.Label>Pro Plan</RadioCard.Label>
<RadioCard.Badge>Popular</RadioCard.Badge>
</RadioCard.TitleRow>
<RadioCard.Description>
Advanced features for professionals
</RadioCard.Description>
</RadioCard.Content>
</RadioCard.Leading>
<RadioCard.RightSlot>
<span className="font-semibold">$19/mo</span>
</RadioCard.RightSlot>
<RadioCard.Indicator />
</RadioCard.Item>
</RadioCard.Group>Available Compound Components
| Component | Description |
|---|---|
RadioCard.Group | Root container wrapping all items |
RadioCard.Item | Individual card wrapper |
RadioCard.Indicator | The radio button indicator |
RadioCard.Label | Label text |
RadioCard.Description | Secondary description text |
RadioCard.Badge | Badge component with state-aware styling |
RadioCard.Content | Container for label, description, and badge |
RadioCard.TitleRow | Horizontal row for label and badge |
RadioCard.Leading | Container for leading content (icon/avatar + text) |
RadioCard.Icon | Circular icon container |
RadioCard.Avatar | Circular avatar container |
RadioCard.Provider | Payment provider icon container |
RadioCard.RightSlot | Custom content on the right side |
Accessibility
- Built on Radix UI Radio Group primitive for robust accessibility
- Full keyboard support (Arrow keys to navigate, Space to select)
- When
clickableCard={true}, the entire card is interactive via a label element - Proper ARIA attributes for screen readers
- Focus states are clearly visible
- Only one option can be selected at a time within a group
API Reference
SimpleRadioCardGroup Props
| Prop | Type | Default | Description |
|---|---|---|---|
| value | string | - | The controlled selected value |
| defaultValue | string | - | The default selected value (uncontrolled) |
| onValueChange | (value: string) => void | - | Callback when selection changes |
| disabled | boolean | false | Disable all cards in the group |
| orientation | "horizontal" | "vertical" | "vertical" | Layout orientation |
| name | string | - | Name for form submission |
| className | string | - | Additional CSS classes |
SimpleRadioCard Props
| Prop | Type | Default | Description |
|---|---|---|---|
| value | string | Required | The value of this option |
| label | string | Required | The main label text |
| subtext | string | - | Secondary descriptive text |
| badge | string | - | Badge text displayed next to the label |
| variant | "text" | "icon" | "avatar" | "cardProvider" | "text" | Visual variant |
| disabled | boolean | false | Whether the card is disabled |
| error | boolean | false | Whether to show error styling |
| clickableCard | boolean | true | Whether clicking the card selects the radio |
| icon | ReactNode | - | Icon element for the icon variant |
| avatar | ReactNode | - | Avatar element for the avatar variant |
| cardProvider | ReactNode | - | Provider icon for the cardProvider variant |
| rightSlot | ReactNode | - | Custom content in the right slot |
| className | string | - | Additional CSS classes |
RadioCard.Group Props
Same as SimpleRadioCardGroup - wraps Radix UI RadioGroup.Root.
RadioCard.Item Props
| Prop | Type | Default | Description |
|---|---|---|---|
| value | string | Required | The value of this option |
| disabled | boolean | false | Whether the card is disabled |
| error | boolean | false | Whether to show error styling |
| clickableCard | boolean | true | Whether clicking the card selects the radio |
| className | string | - | Additional CSS classes |
Context Hooks
| Hook | Description |
|---|---|
useRadioCardGroupContext() | Access group-level state (value, disabled, name) |
useRadioCardItemContext() | Access item-level state (value, disabled, error, isSelected, state) |