Card
Containers for stats, records and media, from static to interactive.
Playground
Cards are built from slots: header (title, description, action), content and footer. Only interactive cards lift on hover, and the whole card is one link whose focus ring wraps the card.
Props
1import { Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"23<Card className="relative transition-[box-shadow,translate] hover:-translate-y-px hover:shadow-lift has-[a[data-card-link]:focus-visible]:ring-2 has-[a[data-card-link]:focus-visible]:ring-ring">4 <CardHeader>5 <CardTitle>6 <Link href="/projects/northwind" data-card-link className="outline-none after:absolute after:inset-0 after:rounded-xl">7 Northwind onboarding8 </Link>9 </CardTitle>10 <CardDescription>Customer portal, due Oct 14</CardDescription>11 <CardAction>12 {/* DropdownMenu trigger, raised above the link with "relative z-10" */}13 <Button variant="ghost" size="icon-sm" aria-label="Project actions"><Ellipsis /></Button>14 </CardAction>15 </CardHeader>16 <CardContent>{/* tags and progress */}</CardContent>17 <CardFooter className="justify-between">18 <AvatarStack names={team} size="xs" />19 <span className="text-xs text-muted-foreground">Updated 2h ago</span>20 </CardFooter>21</Card>Variants
Pick by job, not by look. Borders separate, shadows only answer the pointer. Radius stays at 12px for every card so nested controls (8px) read as inside.
Default: static content on the sheet
Subtle: secondary groups and callouts
Stat: one number with context
Revenue
Up+12%$2.21M
vs Oct 2024 – Sep 2025
Panel: widgets with a title and an action
Renewals this month
Updated 5 min ago
- InitechOct 2$4,120
- HooliOct 9$56,000
- SoylentOct 21$2,980
Record: a person or company at a glance
Media: a project with a rendered preview, clickable as a whole
Selectable: radio cards for a few rich options
Engagement type
States
The interactive card at rest and in each state. A selected card uses the brand ring, the same signal as selection everywhere else.
- Default
rest - Hover
shadow-lift, -translate-y-px - Focus
has(a:focus-visible) - Loading
aria-busy - Disabled
aria-disabled - Error
role=alert - Success
selected
Keyboard
- Focus the card link, then its secondary action
- Tab
- Open the card
- Enter
- Move between radio cards and select
- ←or→
- Select the focused radio card
- Space
Semantics and ARIA
- Interactive cards use one real
<a>on the title stretched over the card withafter:inset-0, so the accessible name is the title, not the whole card's text. - Secondary actions sit above the stretched link with
relative z-10and stay separate tab stops; never nest a button inside the link. - Focus is shown on the card itself with
has-[a:focus-visible], so the ring follows the visual target. - Radio cards are buttons with
role="radio"andaria-checkedinside a labelledradiogroup, with one tab stop and arrow-key selection. - Media previews are
role="img"with a descriptive label; decorative icons arearia-hidden.