Card
Elevated surface for grouping related content. Compose Header, Content, and Footer slots freely.
Examples
Spacing
Use negative margins with -mx-(--card-spacing) to make content go edge to edge while keeping it aligned with the card inset. When the edge-to-edge content sits above a footer, use -mb-(--card-spacing) on CardContent to remove the section gap.
import { Button } from "@codefast/ui/button";
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@codefast/ui/card";
export function CardEdgeToEdge() {
return (
<Card className="mx-auto w-full max-w-sm">
<CardHeader>
<CardTitle>Terms of Service</CardTitle>
<CardDescription>Review the terms before accepting the agreement.</CardDescription>
</CardHeader>
<CardContent className="-mb-(--card-spacing)">
<div className="-mx-(--card-spacing) max-h-48 space-y-4 overflow-y-scroll border-t bg-muted/50 px-(--card-spacing) py-4 text-sm leading-relaxed">
<p>
These terms govern your use of the workspace, including access to shared documents, project files, and
collaboration tools.
</p>
<p>
You are responsible for the content you upload and for ensuring that your team has the appropriate
permissions to view or edit it.
</p>
<p>
We may update features or limits as the service evolves. When those changes materially affect your workflow,
we will notify your workspace administrators.
</p>
<p>
By continuing, you agree to keep your account credentials secure and to follow your organization's
acceptable use policies.
</p>
</div>
</CardContent>
<CardFooter className="justify-end gap-2">
<Button variant="outline">Decline</Button>
<Button>Accept</Button>
</CardFooter>
</Card>
);
}
Image
Add an image before the card header to create a card with an image.
import { Badge } from "@codefast/ui/badge";
import { Button } from "@codefast/ui/button";
import { Card, CardAction, CardDescription, CardFooter, CardHeader, CardTitle } from "@codefast/ui/card";
import { Image } from "@unpic/react";
export function CardImage() {
return (
<Card className="relative mx-auto w-full max-w-sm pt-0">
<div className="absolute inset-0 z-30 aspect-video bg-black/35" />
<Image
alt="Event cover"
className="relative z-20 aspect-video w-full object-cover brightness-60 grayscale dark:brightness-40"
height={360}
layout="constrained"
src="https://images.unsplash.com/photo-1494337480532-3725c85fd2ab?auto=format&fit=crop&w=640&q=80"
width={640}
/>
<CardHeader>
<CardAction>
<Badge variant="secondary">Featured</Badge>
</CardAction>
<CardTitle>Design systems meetup</CardTitle>
<CardDescription>A practical talk on component APIs, accessibility, and shipping faster.</CardDescription>
</CardHeader>
<CardFooter>
<Button className="w-full">View Event</Button>
</CardFooter>
</Card>
);
}
RTL
Right-to-left layout support for languages such as Arabic and Hebrew.
Translations are AI-generated for demonstration and may be imperfect.
import { Button } from "@codefast/ui/button";
import { Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@codefast/ui/card";
import { Input } from "@codefast/ui/input";
import { Label } from "@codefast/ui/label";
import type { Translations } from "#/features/components-catalog/components/detail/language";
import { useTranslation } from "#/features/components-catalog/components/detail/language-context";
const translations: Translations = {
en: {
dir: "ltr",
values: {
title: "Login to your account",
description: "Enter your email below to login to your account",
signUp: "Sign Up",
email: "Email",
emailPlaceholder: "m@example.com",
password: "Password",
forgotPassword: "Forgot your password?",
login: "Login",
loginWithGoogle: "Login with Google",
},
},
ar: {
dir: "rtl",
values: {
title: "تسجيل الدخول إلى حسابك",
description: "أدخل بريدك الإلكتروني أدناه لتسجيل الدخول إلى حسابك",
signUp: "إنشاء حساب",
email: "البريد الإلكتروني",
emailPlaceholder: "m@example.com",
password: "كلمة المرور",
forgotPassword: "نسيت كلمة المرور؟",
login: "تسجيل الدخول",
loginWithGoogle: "تسجيل الدخول باستخدام Google",
},
},
he: {
dir: "rtl",
values: {
title: "התחבר לחשבון שלך",
description: "הזן את האימייל שלך למטה כדי להתחבר לחשבון שלך",
signUp: "הירשם",
email: "אימייל",
emailPlaceholder: "m@example.com",
password: "סיסמה",
forgotPassword: "שכחת את הסיסמה?",
login: "התחבר",
loginWithGoogle: "התחבר עם Google",
},
},
};
export function CardRtl() {
const { dir, t } = useTranslation(translations, "ar");
return (
<Card className="w-full max-w-sm" dir={dir}>
<CardHeader>
<CardTitle>{t.title}</CardTitle>
<CardDescription>{t.description}</CardDescription>
<CardAction>
<Button variant="link">{t.signUp}</Button>
</CardAction>
</CardHeader>
<CardContent>
<form>
<div className="flex flex-col gap-6">
<div className="grid gap-2">
<Label htmlFor="email-rtl">{t.email}</Label>
<Input id="email-rtl" type="email" placeholder={t.emailPlaceholder} required />
</div>
<div className="grid gap-2">
<div className="flex items-center">
<Label htmlFor="password-rtl">{t.password}</Label>
<a href="/" className="ms-auto inline-block text-sm underline-offset-4 hover:underline">
{t.forgotPassword}
</a>
</div>
<Input id="password-rtl" type="password" required />
</div>
</div>
</form>
</CardContent>
<CardFooter className="flex-col gap-2">
<Button type="submit" className="w-full">
{t.login}
</Button>
<Button variant="outline" className="w-full">
{t.loginWithGoogle}
</Button>
</CardFooter>
</Card>
);
}
Size
Use the size='sm' prop to set the size of the card to small. The small size variant uses smaller spacing.
import { Button } from "@codefast/ui/button";
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@codefast/ui/card";
export function CardSmall() {
return (
<Card size="sm" className="mx-auto w-full max-w-sm">
<CardHeader>
<CardTitle>Small Card</CardTitle>
<CardDescription>This card uses the small size variant.</CardDescription>
</CardHeader>
<CardContent>
<p>The card component supports a size prop that can be set to "sm" for a more compact appearance.</p>
</CardContent>
<CardFooter>
<Button variant="outline" size="sm" className="w-full">
Action
</Button>
</CardFooter>
</Card>
);
}
Spacing
In addition to the size prop, you can use the --card-spacing CSS variable to control the spacing between sections and the inset of card parts.
import { Button } from "@codefast/ui/button";
import { Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@codefast/ui/card";
import { Input } from "@codefast/ui/input";
import { Label } from "@codefast/ui/label";
import { ToggleGroup, ToggleGroupItem } from "@codefast/ui/toggle-group";
import * as React from "react";
const spacingOptions = [
{
className: "[--card-spacing:--spacing(4)]",
label: "16px",
value: "4",
},
{
className: "[--card-spacing:--spacing(5)]",
label: "20px",
value: "5",
},
{
className: "[--card-spacing:--spacing(6)]",
label: "24px",
value: "6",
},
{
className: "[--card-spacing:--spacing(8)]",
label: "32px",
value: "8",
},
];
export function CardSpacing() {
const [spacing, setSpacing] = React.useState("4");
const selectedSpacing = spacingOptions.find((option) => option.value === spacing);
return (
<div className="mx-auto grid w-full max-w-sm gap-4">
<ToggleGroup
type="single"
value={spacing}
onValueChange={(value) => {
if (value) {
setSpacing(value);
}
}}
variant="outline"
size="sm"
className="justify-center"
>
{spacingOptions.map((option) => (
<ToggleGroupItem key={option.value} value={option.value}>
{option.label}
</ToggleGroupItem>
))}
</ToggleGroup>
<Card className={selectedSpacing?.className}>
<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>
<form>
<div className="flex flex-col gap-6">
<div className="grid gap-2">
<Label htmlFor="email-spacing">Email</Label>
<Input id="email-spacing" type="email" placeholder="m@example.com" required />
</div>
<div className="grid gap-2">
<div className="flex items-center">
<Label htmlFor="password-spacing">Password</Label>
<a href="/" className="ms-auto inline-block text-sm underline-offset-4 hover:underline">
Forgot your password?
</a>
</div>
<Input id="password-spacing" type="password" required />
</div>
</div>
</form>
</CardContent>
<CardFooter className="flex-col gap-2">
<Button type="submit" className="w-full">
Login
</Button>
<Button variant="outline" className="w-full">
Login with Google
</Button>
</CardFooter>
</Card>
</div>
);
}
Usage
The minimal import and composition — see Examples below for styled, real-world variants.
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@codefast/ui/card";
export function CardUsage() {
return (
<Card className="w-full max-w-sm">
<CardHeader>
<CardTitle>Card Title</CardTitle>
<CardDescription>Card description goes here.</CardDescription>
</CardHeader>
<CardContent>
<p>Card content goes here.</p>
</CardContent>
</Card>
);
}
Anatomy
How the parts nest — every slot the component exposes, in composition order.
Features
- Two sizes (default, sm) via the --card-spacing CSS variable, adjustable per-instance without a new size value.
- CardHeader switches to a 2-column grid automatically when a CardAction is present, and reserves a second row when a CardDescription follows the title.
- A leading or trailing <img> direct child gets its corners rounded to match the card automatically.
API reference
Props for each part of the component. All native element props are also forwarded.
Card
The surface. Every part below is a styled <div> you can omit or reorder.
classNamestringCompose width, shadow, and spacing — the card owns no layout of its own.
CardHeader
Hosts CardTitle, CardDescription, and an optional CardAction.
childrenReactNodeCardTitle, CardDescription, and an optional CardAction.
CardFooter
The bottom bar, set off from CardContent by a border and muted background.
childrenReactNodeFooter actions or supplementary content.
CardContent
The main body of the card.
childrenReactNodeThe card's primary content.
Accessibility
Built to be keyboard-navigable and screen-reader friendly out of the box.
- Card is a presentational container with no implicit role — structure content with real headings.
- When a whole card is clickable, wrap it in a single link/button rather than nesting interactives.
- Use CardTitle as a real heading level that fits the surrounding document outline.
Guidelines
Conventions that keep usage consistent across an app.
Do
- Group genuinely related content; one idea per card.
- Keep actions in CardFooter or CardAction so they’re easy to find.
Don’t
- Don’t nest cards inside cards — it muddies hierarchy.
- Don’t put more than one primary action in a single card.
Explore further
Ready to integrate?
Follow the Getting Started guide to install @codefast/ui, or browse the full component gallery.