Card
Displays a container with optional header, content, and footer sections.
Project settings
Manage how this project behaves.
Changes apply to every member of the project.
TSXimport { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; export default function CardDemo() { return ( <Card className="w-full max-w-sm"> <CardHeader> <CardTitle>Project settings</CardTitle> <CardDescription>Manage how this project behaves.</CardDescription> </CardHeader> <CardContent> <p className="text-sm text-muted-foreground"> Changes apply to every member of the project. </p> </CardContent> <CardFooter className="justify-end gap-2"> <Button variant="outline">Cancel</Button> <Button>Save</Button> </CardFooter> </Card> ); }
Installation
Bashnpx shadcn@latest add @sevenui/cardpnpm dlx shadcn@latest add @sevenui/cardyarn dlx shadcn@latest add @sevenui/cardbunx --bun shadcn@latest add @sevenui/card
Usage
TSXimport { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, } from "@/components/ui/card"; <Card> <CardHeader> <CardTitle>Card Title</CardTitle> <CardDescription>Card Description</CardDescription> </CardHeader> <CardContent>Card content goes here.</CardContent> <CardFooter>Card footer goes here.</CardFooter> </Card>;
Examples
Login form
CardAction places the “Sign up” button in the header’s top-right
corner.
Login to your account
Enter your email below to login to your account
TSX"use client"; import { Button } from "@/components/ui/button"; import { Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; export default function CardLogin() { return ( <Card className="w-full max-w-sm"> <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 className="flex flex-col gap-4"> <div className="flex flex-col gap-2"> <Label htmlFor="card-login-email">Email</Label> <Input id="card-login-email" type="email" placeholder="you@example.com" /> </div> <div className="flex flex-col gap-2"> <div className="flex items-center justify-between"> <Label htmlFor="card-login-password">Password</Label> <a href="#forgot" className="text-xs underline underline-offset-3"> Forgot your password? </a> </div> <Input id="card-login-password" type="password" /> </div> </form> </CardContent> <CardFooter className="flex-col gap-2"> <Button className="w-full">Login</Button> <Button variant="outline" className="w-full"> Login with Google </Button> </CardFooter> </Card> ); }
Stats
Compact metric cards using size="sm" with trend badges as header
actions.
Total revenue
$1,250.00
+12.5%
Trending up this month
New customers
1,234
-20%
Down from last period
TSX"use client"; import { TrendingDownIcon, TrendingUpIcon } from "lucide-react"; import { Badge } from "@/components/ui/badge"; import { Card, CardAction, CardDescription, CardFooter, CardHeader, CardTitle, } from "@/components/ui/card"; const stats = [ { label: "Total revenue", value: "$1,250.00", trend: "+12.5%", up: true, note: "Trending up this month", }, { label: "New customers", value: "1,234", trend: "-20%", up: false, note: "Down from last period", }, ]; export default function CardStats() { return ( <div className="grid w-full max-w-lg gap-4 sm:grid-cols-2"> {stats.map((stat) => ( <Card key={stat.label} size="sm"> <CardHeader> <CardDescription>{stat.label}</CardDescription> <CardTitle className="text-2xl font-semibold tabular-nums"> {stat.value} </CardTitle> <CardAction> <Badge variant="outline"> {stat.up ? <TrendingUpIcon /> : <TrendingDownIcon />} {stat.trend} </Badge> </CardAction> </CardHeader> <CardFooter className="text-xs text-muted-foreground"> {stat.note} </CardFooter> </Card> ))} </div> ); }
With image
A first-child image renders edge-to-edge with rounded top corners.
Designing with constraints
Why limitations make interfaces better, not worse.
Grids, type scales, and spacing tokens do the heavy lifting so every screen feels like part of the same product.
TSX"use client"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, } from "@/components/ui/card"; export default function CardImage() { return ( <Card className="w-full max-w-sm"> <img src="/placeholder.svg" alt="" className="aspect-video object-cover" /> <CardHeader> <CardTitle>Designing with constraints</CardTitle> <CardDescription> Why limitations make interfaces better, not worse. </CardDescription> </CardHeader> <CardContent className="text-muted-foreground"> Grids, type scales, and spacing tokens do the heavy lifting so every screen feels like part of the same product. </CardContent> <CardFooter> <Button variant="outline" size="sm"> Read article </Button> </CardFooter> </Card> ); }
Settings
Notifications
Choose what you want to hear about.
When someone replies to your thread
When someone mentions you
Summary of activity every Monday
TSX"use client"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@/components/ui/card"; import { Label } from "@/components/ui/label"; import { Switch } from "@/components/ui/switch"; const settings = [ { id: "card-notify-comments", label: "Comments", description: "When someone replies to your thread", defaultChecked: true, }, { id: "card-notify-mentions", label: "Mentions", description: "When someone mentions you", defaultChecked: true, }, { id: "card-notify-digest", label: "Weekly digest", description: "Summary of activity every Monday", defaultChecked: false, }, ]; export default function CardNotifications() { return ( <Card className="w-full max-w-sm"> <CardHeader> <CardTitle>Notifications</CardTitle> <CardDescription>Choose what you want to hear about.</CardDescription> </CardHeader> <CardContent className="flex flex-col gap-4"> {settings.map((setting) => ( <div key={setting.id} className="flex items-start justify-between gap-4"> <div className="flex flex-col gap-0.5"> <Label htmlFor={setting.id}>{setting.label}</Label> <span className="text-xs text-muted-foreground"> {setting.description} </span> </div> <Switch id={setting.id} defaultChecked={setting.defaultChecked} /> </div> ))} </CardContent> </Card> ); }
Project
Design system refresh
Due Friday · 8 tasks left
ECJLSR
In progressTSX"use client"; import { MoreHorizontalIcon } from "lucide-react"; import { Avatar, AvatarFallback } from "@/components/ui/avatar"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card, CardAction, CardContent, CardDescription, CardHeader, CardTitle, } from "@/components/ui/card"; export default function CardProject() { return ( <Card size="sm" className="w-full max-w-sm"> <CardHeader> <CardTitle>Design system refresh</CardTitle> <CardDescription>Due Friday · 8 tasks left</CardDescription> <CardAction> <Button variant="ghost" size="icon-sm" aria-label="Project options"> <MoreHorizontalIcon /> </Button> </CardAction> </CardHeader> <CardContent className="flex items-center justify-between"> <div className="flex -space-x-2"> {["EC", "JL", "SR"].map((initials) => ( <Avatar key={initials} className="size-6 ring-2 ring-card"> <AvatarFallback className="text-[0.6rem]"> {initials} </AvatarFallback> </Avatar> ))} </div> <Badge variant="secondary">In progress</Badge> </CardContent> </Card> ); }
API reference
Card
Extends div. The container also handles edge-to-edge images: an img
as first or last child gets rounded corners and no padding.
| Prop | Type | Default |
|---|---|---|
size | "default" | "sm" | "default" |
sm tightens the internal spacing for compact cards.
CardHeader, CardTitle, CardDescription, CardAction, CardContent, CardFooter
Structural wrappers extending div. CardHeader switches to a
two-column grid when a CardAction is present, placing the action in
the top-right corner.