Radio cards
Selectable option cards with a sliding selection ring, price and description slots, and radio keyboard behavior.
inputsnew
Preview
Radio cards preview
Live · keyboard ready
FreeThe whole core library.
ProMotion components and larger blocks.
When to use
- Choices where each option needs a description, price, or estimate to decide.
- Plan, shipping, region, or size pickers inside checkout and setup flows.
- Options that may be unavailable and need a visible reason.
When not to use
- Use radio-group for short text-only options.
- Use segmented-control for two to four view modes that switch content instantly.
- Use select or combobox for long lists.
shadcn CLI
Also installs (1)
- manicat-foundation
Manual install(+)
registry.jsonjson
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "radio-cards",
"type": "registry:ui",
"title": "Radio cards",
"description": "Selectable option cards with a sliding selection ring, price and description slots, and radio keyboard behavior.",
"categories": [
"inputs"
],
"dependencies": [
"motion"
],
"registryDependencies": [
"https://manicat.viraza.net/r/manicat-foundation.json"
],
"files": [
{
"path": "registry/components/radio-cards/radio-cards.tsx",
"type": "registry:component",
"target": "@components/manicat/radio-cards/radio-cards.tsx"
},
{
"path": "registry/components/radio-cards/radio-cards.module.css",
"type": "registry:component",
"target": "@components/manicat/radio-cards/radio-cards.module.css"
}
],
"docs": "Docs and live preview: https://uiarc.dev/components/radio-cards",
"meta": {
"tier": "free",
"kind": "component",
"docs": "https://manicat.viraza.net/components/radio-cards",
"markdown": "https://uiarc.dev/components/radio-cards/markdown",
"tags": [
"inputs",
"new"
]
}
}Usage
Install the component, then copy this into your app. Change the content and the props to fit your screen.
example.tsxtsx
"use client";
import { useState } from "react";
import { RadioCards } from "@/components/manicat/radio-cards/radio-cards";
export function RadioCardsExample() {
const [value, setValue] = useState("pro");
return (
<RadioCards
aria-label="Plan"
value={value}
onValueChange={setValue}
options={[
{ value: "free", label: "Free", description: "The whole core library." },
{ value: "pro", label: "Pro", description: "Motion components and larger blocks." },
]}
/>
);
}
Examples
Real screens rather than isolated props. Switch to Code to copy a whole example into your project.
Plan selection Bigger touch targets for choices that deserve a sentence of explanation.
Plan selection
Bigger touch targets for choices that deserve a sentence of explanation. preview
Live · keyboard ready
FreeThe whole core library.
ProMotion components and larger blocks.
TeamShared tokens and priority support.
plan-selection-bigger-touch-targets-for-choices-that-deserve-a-sentence-of-explanation-.tsxtsx
"use client";
import { useState } from "react";
import { RadioCards } from "@/components/manicat/radio-cards/radio-cards";
export function RadioCardsPlanExample() {
const [value, setValue] = useState("pro");
return (
<div className="w-full max-w-md">
<RadioCards
aria-label="Plan"
value={value}
onValueChange={setValue}
options={[
{ value: "free", label: "Free", description: "The whole core library." },
{ value: "pro", label: "Pro", description: "Motion components and larger blocks." },
{ value: "team", label: "Team", description: "Shared tokens and priority support." },
]}
/>
</div>
);
}
Keyboard interactions
| Keys | Action |
|---|---|
| Tab | Focuses the selected card, or the first enabled one. One tab stop for the group. |
| Arrow keys | Move to the next or previous enabled card and select it, wrapping. Left and right flip in RTL. |
| Home / End | Select the first or last enabled card. |
| Enter / Space | Selects the focused card. |
Accessibility
- role="radiogroup" with role="radio" cards, aria-checked, and aria-disabled; give the group an aria-label or aria-labelledby.
- Each card is labelled by its label and described by its description, or by disabledReason when disabled.
- Selection is shown by a ring and a filled indicator dot, not color alone.
Motion
- One ring springs to the selected card's box and its size; resizes snap it without replaying the glide.
- The indicator dot scales in on the snappy spring.
- Reduced motion jumps the ring and dot to their final state.
Responsive behavior
- The grid uses auto-fill columns with a minimum of minColumnWidth, so it drops to one column in narrow containers.
- List layout keeps the indicator, text, and meta on one row and truncates nothing; long descriptions wrap.
Performance
- One ResizeObserver on the group keeps the ring aligned; the ring moves with motion values without re-rendering cards.
Notes for AI
- Choose it for three to six options that each need a description or price: plans, shipping speeds, regions, instance sizes.
- Pass name to submit with a native form; the value is carried by a hidden input.
- Use disabledReason to explain why an option is unavailable instead of hiding it.