Radio cards

Selectable option cards with a sliding selection ring, price and description slots, and radio keyboard behavior.

inputsnew
Preview
Radio cards preview

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

Keyboard interactions

KeysAction
TabFocuses the selected card, or the first enabled one. One tab stop for the group.
Arrow keysMove to the next or previous enabled card and select it, wrapping. Left and right flip in RTL.
Home / EndSelect the first or last enabled card.
Enter / SpaceSelects 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.