Card
A card that holds one thing: a staff member, a booking, a group of settings. The card tint, one tint of ink with no edge, as a card's sections, with squircle corners, in two sizes. Built on the vendored shadcn card.
Playground
import {
Card, CardHeader, CardTitle, CardDescription, CardAction,
CardContent, CardFooter,
} from "@/components/shared/card";
<Card>
<CardHeader>
<CardTitle>Anna Kováčová</CardTitle>
<CardDescription>Senior stylist</CardDescription>
<CardAction>
<Switch checked={active} onCheckedChange={setActive} aria-label="Active: Anna Kováčová" />
</CardAction>
</CardHeader>
<CardContent>
{hours.map((h) => <Badge key={h} variant="outline">{h}</Badge>)}
</CardContent>
<CardFooter>
<Button variant="secondary" size="sm">Edit hours</Button>
</CardFooter>
</Card>Anatomy
A header holds the title, an optional description, and an optional action pinned top-right. Content comes next, then an optional footer: a porcelain band for the card's own actions. Every part is optional; the card spaces whatever it holds.
import {
Card, CardHeader, CardTitle, CardDescription, CardAction, CardContent, CardFooter,
} from "@/components/shared/card";
<Card>
<CardHeader>
<CardTitle>Title</CardTitle>
<CardDescription>Description</CardDescription>
<CardAction>Action</CardAction>
</CardHeader>
<CardContent>Content</CardContent>
<CardFooter>Footer</CardFooter>
</Card>Sizes
md spaces its parts 16px apart and from the edge. sm tightens that to 12px and steps the title down to 14px, for cards packed in a grid or a narrow column.
import {
Card, CardHeader, CardTitle, CardDescription, CardAction, CardContent, CardFooter,
} from "@/components/shared/card";
<Card>…</Card>
<Card size="sm">…</Card>In a grid
Packed in a grid, cards step down to sm. A figure reads best large and in tabular figures, in the content, with its label above it as the description.
import {
Card, CardHeader, CardTitle, CardDescription, CardAction, CardContent, CardFooter,
} from "@/components/shared/card";
<div className="grid gap-4 sm:grid-cols-3">
{stats.map(([label, value, note]) => (
<Card key={label} size="sm">
<CardHeader>
<CardDescription>{label}</CardDescription>
</CardHeader>
<CardContent>
<p className="text-2xl font-medium tracking-tight tabular-nums">{value}</p>
<p className="text-muted-foreground text-xs">{note}</p>
</CardContent>
</Card>
))}
</div>With a footer
The footer holds the card's own actions, trailing, a step down from the page's one primary action.
import {
Card, CardHeader, CardTitle, CardDescription, CardAction, CardContent, CardFooter,
} from "@/components/shared/card";
import { Button } from "@/components/shared/button";
<Card className="w-full max-w-sm">
<CardHeader>
<CardTitle>Balayage</CardTitle>
<CardDescription>Thursday 15 October, 14:00–16:30 · with Anna</CardDescription>
</CardHeader>
<CardContent>Petra Nováková · +421 911 234 567</CardContent>
<CardFooter className="justify-end">
<Button variant="ghost" size="sm">Cancel booking</Button>
<Button variant="secondary" size="sm">Reschedule</Button>
</CardFooter>
</Card>API reference
| Prop | Type | Default | Description |
|---|---|---|---|
| size | "sm" | "md" | "md" | Card only. The inset around and between its parts: 12 or 16px. sm also steps the title down to 14px. |
| flush | boolean | false | Card only. Its last part runs to the bottom edge, with no inset under it: a table. |
| level | 1 | 2 | 3 | 4 | 5 | 6 | — | CardTitle only. Makes the title a heading at this level (role and aria-level), for the page's outline. |
| className | string | — | Layout on any part: width, grid placement, flex inside CardContent or CardFooter. Never restyle the surface. |
Every part renders a <div> and passes everything else through. CardAction sits top-right of the header, across the title and description.
Guidelines
- A card holds one thing. When it would hold a list of like things, use a Table, and put it in a card only if it needs a title.
- Don't nest cards. Inside a card, group with space or a Separator.
- A whole card is never a link or a button. Put the action in the header's CardAction or in the footer, where it can be focused and named.
- Give the title a
levelwhen the card heads a section, like a staff member on the team page, so screen-reader users can jump between cards.