[!NOTE] When using accent colors (like icons or highlights), they will automatically use
bg-primary(Helix navy blue).
Import
import {
Card,
CardAction,
CardHeader,
CardTitle,
CardDescription,
CardContent,
CardFooter,
} from "@timelycare/helix-ui"
Props
All Card sub-components extend React.ComponentProps<"div"> — they accept any standard div attribute plus className for styling overrides.
// Card — root container
function Card({ className, ...props }: React.ComponentProps<"div">)
// CardHeader — title, description, and optional action
function CardHeader({ className, ...props }: React.ComponentProps<"div">)
// CardTitle — card heading text
function CardTitle({ className, ...props }: React.ComponentProps<"div">)
// CardDescription — helper text under the title
function CardDescription({ className, ...props }: React.ComponentProps<"div">)
// CardAction — top-right header slot for secondary actions
function CardAction({ className, ...props }: React.ComponentProps<"div">)
// CardContent — main card body
function CardContent({ className, ...props }: React.ComponentProps<"div">)
// CardFooter — actions and secondary content at the bottom
function CardFooter({ className, ...props }: React.ComponentProps<"div">)
Variants
| Variant | Use For | Tailwind |
|---|---|---|
default | All standard card implementations | bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm |
Choosing a Variant
Cards are container components - visual variations come from content composition, not card variants.
Styling
Card Container
| Property | Value | Tailwind |
|---|---|---|
| Layout | Vertical flex column | flex flex-col |
| Background | bg-card | Light: white, Dark: #18181b |
| Border | 1px solid | border |
| Border Radius | 12px | rounded-xl |
| Shadow | Small shadow | shadow-sm |
| Section Gap | 24px | gap-6 |
| Vertical Padding | 24px | py-6 |
| Horizontal Padding | 0px | Content sections handle padding via px-6 |
Typography
| Element | Weight | Size | Line Height | Color |
|---|---|---|---|---|
| Title | font-semibold (600) | Inherited (base) | leading-none | text-card-foreground (inherited) |
| Description | Normal (400) | text-sm (14px) | Default text-sm line height | text-muted-foreground |
Spacing
| Property | Tailwind | Pixels |
|---|---|---|
| Card vertical padding | py-6 | 24px |
| Section horizontal padding | px-6 | 24px |
| Gap between sections | gap-6 | 24px |
| CardHeader internal gap (title to description) | gap-2 | 8px |
Sizes
The Card component does not have a size prop. It uses a single default size. To create denser cards, override spacing via className.
| Size | Spacing | Use For |
|---|---|---|
default | Standard padding (py-6, px-6, gap-6) | All card uses — forms, content, dashboards |
Width
| Approach | Use For | Notes |
|---|---|---|
| Flexible | All use cases | Width/height adapt to container and content |
| Fixed Width | Forms, dialogs | Use w-[420px] or similar constraints |
Header Variants
Text Only (Default)
<CardHeader>
<CardTitle>Title Text</CardTitle>
<CardDescription>This is a card description.</CardDescription>
</CardHeader>
- CardHeader uses CSS Grid layout (
grid auto-rows-min grid-rows-[auto_auto]) - Title/description stack vertically with
gap-2(8px)
With CardAction
CardAction places content (button, badge, link) in the top-right corner of the header:
<CardHeader>
<CardTitle>Title Text</CardTitle>
<CardDescription>This is a card description.</CardDescription>
<CardAction>
<Button variant="link">Sign Up</Button>
</CardAction>
</CardHeader>
- When
CardActionis present, CardHeader switches to a two-column grid viahas-data-[slot=card-action]:grid-cols-[1fr_auto] CardActionis positioned at the inline-end of the header row (col-start-2 row-span-2 row-start-1)- Use for secondary actions: links, badges, icon buttons
With Icon
<CardHeader className="flex-row gap-2 items-center">
<Icon className="size-12 text-primary" />
<div className="flex flex-col gap-1">
<CardTitle>Title Text</CardTitle>
<CardDescription>This is a card description.</CardDescription>
</div>
</CardHeader>
- Icon size:
size-12(48×48px) - Icon color:
text-primary(Helix navy) - Gap between icon and text:
gap-2(8px)
States
| State | Implementation |
|---|---|
| Default | Base appearance with shadow |
| Disabled | opacity-50 pointer-events-none on Card container |
Cards are container components with no interactive states. Child elements (buttons, links) have their own states.
Disabled Card
<Card className="opacity-50 pointer-events-none">
<CardHeader>
<CardTitle>Premium Feature</CardTitle>
<CardDescription>Upgrade to access this feature.</CardDescription>
</CardHeader>
<CardContent>
{/* Content */}
</CardContent>
</Card>
Common Patterns
Basic Card
<Card>
<CardHeader>
<CardTitle>Title Text</CardTitle>
<CardDescription>This is a card description.</CardDescription>
</CardHeader>
<CardContent>
<p>Your content goes here.</p>
</CardContent>
<CardFooter>
<Button>Action</Button>
</CardFooter>
</Card>
Card with Icon Header
<Card className="w-[420px]">
<CardHeader className="flex-row gap-2 items-center">
<SquareTerminal className="size-12 text-primary" />
<div className="flex flex-col gap-1">
<CardTitle>Title Text</CardTitle>
<CardDescription>This is a card description.</CardDescription>
</div>
</CardHeader>
<CardContent>
<p>Your content goes here.</p>
</CardContent>
<CardFooter>
<Button>Action</Button>
</CardFooter>
</Card>
Login Card with CardAction
<Card className="w-[420px]">
<CardHeader>
<CardTitle>Login to your account</CardTitle>
<CardDescription>
Enter your email below to login to your account
</CardDescription>
<CardAction>
<Button variant="link">Sign Up</Button>
</CardAction>
</CardHeader>
<CardContent className="space-y-6">
<div className="space-y-2">
<Label htmlFor="email">Email</Label>
<Input id="email" placeholder="m@example.com" />
</div>
<div className="space-y-2">
<div className="flex justify-between">
<Label htmlFor="password">Password</Label>
<a href="#" className="text-sm text-muted-foreground underline">
Forgot your password?
</a>
</div>
<Input id="password" type="password" />
</div>
</CardContent>
<CardFooter className="flex-col gap-2">
<Button className="w-full">Login</Button>
<Button variant="outline" className="w-full">Login with Google</Button>
<p className="text-sm text-card-foreground pt-4 text-center">
Don't have an account? <a href="#" className="underline">Sign up</a>
</p>
</CardFooter>
</Card>
Card with Full-Bleed Image
<Card className="w-[420px]">
<CardHeader className="flex-row gap-2 items-center">
<SquareTerminal className="size-12 text-primary" />
<div className="flex flex-col gap-1">
<CardTitle>Is this an image?</CardTitle>
<CardDescription>This is a card with an image.</CardDescription>
</div>
</CardHeader>
<CardContent className="px-0">
<AspectRatio ratio={16/9}>
<img
src="/image.jpg"
alt="Property"
className="absolute inset-0 size-full object-cover"
/>
</AspectRatio>
</CardContent>
<CardFooter className="flex justify-between">
<div className="flex gap-2">
<Badge variant="outline"><Bed className="size-3" /> 4</Badge>
<Badge variant="outline"><Bath className="size-3" /> 2</Badge>
<Badge variant="outline"><LandPlot className="size-3" /> 350m²</Badge>
</div>
<span className="font-semibold">$135,000</span>
</CardFooter>
</Card>
Card with Inline Image
<Card className="w-[420px]">
<CardContent>
<AspectRatio ratio={2/1} className="rounded-md overflow-clip">
<img
src="/image.jpg"
alt="Description"
className="absolute inset-0 size-full object-cover"
/>
</AspectRatio>
</CardContent>
</Card>
- Inline images use
rounded-md(8px) border radius - Full-bleed images remove horizontal padding with
px-0on CardContent
Meeting Notes Card
<Card className="w-[420px]">
<CardHeader className="flex-row gap-2 items-center">
<SquareTerminal className="size-12 text-primary" />
<div className="flex flex-col gap-1">
<CardTitle>Meeting Notes</CardTitle>
<CardDescription>Transcript from the meeting with the client.</CardDescription>
</div>
</CardHeader>
<CardContent>
<div className="text-sm leading-5 text-card-foreground space-y-2">
<p>Client requested dashboard redesign with focus on mobile responsiveness.</p>
<ol className="list-decimal list-inside space-y-1">
<li>New analytics widgets for daily/weekly metrics</li>
<li>Simplified navigation menu</li>
<li>Dark mode support</li>
<li>Timeline: 6 weeks</li>
<li>Follow-up meeting scheduled for next Tuesday</li>
</ol>
</div>
</CardContent>
<CardFooter>
<AvatarGroup />
</CardFooter>
</Card>
Card with Form
Forms should always be wrapped in a Card for visual grouping.
<Card className="max-w-[374px]">
<CardHeader>
<CardTitle>Create Account</CardTitle>
<CardDescription>Enter your details below.</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
<Label htmlFor="email">Email</Label>
<Input id="email" placeholder="name@example.com" />
</div>
<div className="space-y-2">
<Label htmlFor="password">Password</Label>
<Input id="password" type="password" />
</div>
</CardContent>
<CardFooter>
<Button className="w-full">Create Account</Button>
</CardFooter>
</Card>
Form Spacing Rules
- Card padding:
py-6vertical on Card,px-6horizontal on each section - Between fields:
space-y-4(16px) - Label to input:
space-y-2(8px) - Content to footer: automatic via Card structure
Dashboard Data Card
A compact card for displaying a single key metric with a label, prominent value, and optional trend or badge. Uses existing Card sub-components — no new variant needed.
<Card>
<CardHeader className="gap-1.5">
<CardDescription>Total Revenue</CardDescription>
<CardTitle className="text-3xl font-semibold tracking-tight">
$45,231.89
</CardTitle>
</CardHeader>
<CardContent>
<p className="text-xs text-muted-foreground">+20.1% from last month</p>
</CardContent>
</Card>
CardDescriptionrenders beforeCardTitleso the label sits above the valueCardTitleis sized up totext-3xl(30px/36px, Adelle Sans Bold) withtracking-tightCardHeaderusesgap-1.5(6px) for a tighter label-to-value relationship- Trend line in
CardContentusestext-xs text-muted-foreground(12px, Adelle Sans Regular)
Dashboard Card with Badge
Adds a status or trend badge to the header via CardAction:
<Card>
<CardHeader className="gap-1.5">
<CardDescription>Active Users</CardDescription>
<CardTitle className="text-3xl font-semibold tracking-tight">
2,350
</CardTitle>
<CardAction>
<Badge variant="secondary">
<TrendingUp />
+12.5%
</Badge>
</CardAction>
</CardHeader>
<CardContent>
<p className="text-xs text-muted-foreground">+180 since last hour</p>
</CardContent>
</Card>
- Badge sits in
CardAction(top-right corner of the header grid) - Use
variant="secondary"for neutral trends,variant="destructive"for negative trends - Badge icons (
TrendingUp,TrendingDown) auto-size to 12px via[&>svg]:size-3
Dashboard Card with Icon
Places an icon alongside the label for quick visual identification:
<Card>
<CardHeader className="gap-1.5">
<div className="flex items-center justify-between">
<CardDescription>Total Revenue</CardDescription>
<DollarSign className="size-4 text-muted-foreground" />
</div>
<CardTitle className="text-3xl font-semibold tracking-tight">
$45,231.89
</CardTitle>
</CardHeader>
<CardContent>
<p className="text-xs text-muted-foreground">+20.1% from last month</p>
</CardContent>
</Card>
- Icon is
size-4(16px) intext-muted-foreground— keeps it secondary to the value - Placed inline with the label via
flex items-center justify-between
Dashboard Card Grid
Dashboard cards are typically arranged in a responsive grid:
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
<Card>
<CardHeader className="gap-1.5">
<div className="flex items-center justify-between">
<CardDescription>Total Revenue</CardDescription>
<DollarSign className="size-4 text-muted-foreground" />
</div>
<CardTitle className="text-3xl font-semibold tracking-tight">
$45,231.89
</CardTitle>
</CardHeader>
<CardContent>
<p className="text-xs text-muted-foreground">+20.1% from last month</p>
</CardContent>
</Card>
{/* Repeat for each metric */}
</div>
- Use
grid gap-4 sm:grid-cols-2 lg:grid-cols-4for a responsive 1→2→4 column layout - Cards stretch to fill grid cells — no fixed width needed
- Keep
gap-4(16px) between cards for visual breathing room
Dashboard Card Spacing Rules
| Property | Tailwind | Pixels | Notes |
|---|---|---|---|
| Card vertical padding | py-6 | 24px | Default; use py-4 for compact |
| Header internal gap | gap-1.5 | 6px | Tighter than default gap-2 |
| Section horizontal padding | px-6 | 24px | Inherited from Card defaults |
| Grid gap | gap-4 | 16px | Between cards in a grid layout |
Dashboard Card Typography
| Element | Tailwind | Size | Weight | Font |
|---|---|---|---|---|
| Label | text-sm text-muted-foreground | 14px / 20px | Normal (400) | Adelle Sans Regular |
| Value | text-3xl font-semibold tracking-tight | 30px / 36px | Semibold (600) | Adelle Sans Bold |
| Trend text | text-xs text-muted-foreground | 12px / 16px | Normal (400) | Adelle Sans Regular |
| Badge | (inherits from Badge) | 12px / 16px | Medium (500) | Adelle Sans Bold |
Dark Mode
| Element | Token | Light | Dark |
|---|---|---|---|
| Background | bg-card | white | zinc-900 |
| Border | border-border | zinc-200 | white/10 |
| Card Foreground | text-card-foreground | zinc-950 | zinc-50 |
| Muted Foreground | text-muted-foreground | zinc-500 | zinc-400 |
Dark mode colors are applied automatically via CSS variables when the dark class is present on a parent element. Always use semantic tokens (bg-card, text-card-foreground) — never hard-code hex values.
Accessibility
- Styled container — no Radix interactive primitive
- Use semantic elements:
<h3>in CardTitle,<p>in CardDescription - If card is interactive, ensure entire card is focusable or use a link wrapper
- No special ARIA required for static display cards
Gotchas
| Problem | Solution |
|---|---|
| Card too wide | Set explicit width via className="w-[420px]" or use grid/flex container |
| Image has side padding | Add px-0 to CardContent for full-bleed images |
| Footer buttons not aligned | Use flex justify-between or flex-col gap-2 on CardFooter |
| Need compact card | Override spacing via className — e.g. <Card className="py-4 gap-4"> |
| Header icon misaligned | Add flex-row gap-2 items-center to CardHeader |
| Title/description gap wrong | Wrap in div with flex flex-col gap-1 when using icon header |
See Also
- Related Components: Dialog (card with modal), Form (card with form)
- Patterns: Layouts — Card layout patterns
- Tokens: Colors — Theme-aware color tokens
Last updated: February 24, 2026