Card
Displays a card with header, content, and footer.
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 CardDemo() { 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> <div className="flex flex-col gap-6"> <div className="grid gap-2"> <Label htmlFor="email">Email</Label> <Input id="email" type="email" placeholder="m@example.com" required /> </div> <div className="grid gap-2"> <div className="flex items-center"> <Label htmlFor="password">Password</Label> <a href="#" className="ml-auto inline-block text-sm underline-offset-4 hover:underline" > Forgot your password? </a> </div> <Input id="password" 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> );}Installation
npx shadcn@latest add @cortexcn/cardimport * as React from "react";import { cn } from "@/lib/utils";function Card({ className, size = "default", ...props}: React.ComponentProps<"div"> & { size?: "default" | "sm" }) { return ( <div data-slot="card" data-size={size} className={cn( "group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-xl bg-card py-(--card-spacing) text-sm text-card-foreground ring-1 ring-foreground/10 [--card-spacing:--spacing(4)] has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:[--card-spacing:--spacing(3)] data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl", className, )} {...props} /> );}function CardHeader({ className, ...props }: React.ComponentProps<"div">) { return ( <div data-slot="card-header" className={cn( "group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-(--card-spacing) has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-(--card-spacing)", className, )} {...props} /> );}function CardTitle({ className, ...props }: React.ComponentProps<"div">) { return ( <div data-slot="card-title" className={cn("font-semibold leading-none", className)} {...props} /> );}function CardDescription({ className, ...props }: React.ComponentProps<"div">) { return ( <div data-slot="card-description" className={cn("text-sm text-muted-foreground", className)} {...props} /> );}function CardAction({ className, ...props }: React.ComponentProps<"div">) { return ( <div data-slot="card-action" className={cn( "col-start-2 row-span-2 row-start-1 self-start justify-self-end", className, )} {...props} /> );}function CardContent({ className, ...props }: React.ComponentProps<"div">) { return ( <div data-slot="card-content" className={cn("px-(--card-spacing)", className)} {...props} /> );}function CardFooter({ className, ...props }: React.ComponentProps<"div">) { return ( <div data-slot="card-footer" className={cn( "flex items-center rounded-b-xl border-t bg-muted/50 p-(--card-spacing)", className, )} {...props} /> );}export { Card, CardHeader, CardFooter, CardTitle, CardAction, CardDescription, CardContent,};Usage
import {
Card,
CardAction,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card";<Card>
<CardHeader>
<CardTitle>Card Title</CardTitle>
<CardDescription>Card Description</CardDescription>
<CardAction>Card Action</CardAction>
</CardHeader>
<CardContent>
<p>Card Content</p>
</CardContent>
<CardFooter>
<p>Card Footer</p>
</CardFooter>
</Card>Composition
Use the following composition to build a Card:
Card
├── CardHeader
│ ├── CardTitle
│ ├── CardDescription
│ └── CardAction
├── CardContent
└── CardFooterExamples
Size
Use the size="sm" prop to set the card to the compact size variant.
import { Button } from "@/components/ui/button";import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle,} from "@/components/ui/card";export default 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> );}Image
Add an image as the first child of the card to create a card with a cover image.
import { Badge } from "@/components/ui/badge";import { Button } from "@/components/ui/button";import { Card, CardAction, CardDescription, CardFooter, CardHeader, CardTitle,} from "@/components/ui/card";export default 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" /> {/* eslint-disable-next-line @next/next/no-img-element */} <img src="https://avatar.vercel.sh/shadcn1" alt="Event cover" className="relative z-20 aspect-video w-full object-cover brightness-60 grayscale dark:brightness-40" /> <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> );}Edge to Edge
Use negative margins with -mx-(--card-spacing) to make content go edge to edge while staying aligned with the card inset.
import { Button } from "@/components/ui/button";import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle,} from "@/components/ui/card";export default 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> );}Spacing
Use the --card-spacing CSS variable to control the spacing between sections and the inset of card parts.
"use client";import * as React from "react";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";import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";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 default function CardSpacing() { const [spacing, setSpacing] = React.useState("4"); const selectedSpacing = spacingOptions.find((o) => o.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"> <Label htmlFor="password-spacing">Password</Label> <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> </CardFooter> </Card> </div> );}RTL
Cards respect the document direction — pass dir="rtl" to flip the layout.
"use client";import * as React from "react";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";const content = { ltr: { dir: "ltr" as const, title: "Login to your account", description: "Enter your email below to login to your account", signUp: "Sign Up", email: "Email", password: "Password", login: "Login", }, rtl: { dir: "rtl" as const, title: "تسجيل الدخول إلى حسابك", description: "أدخل بريدك الإلكتروني أدناه لتسجيل الدخول إلى حسابك", signUp: "إنشاء حساب", email: "البريد الإلكتروني", password: "كلمة المرور", login: "تسجيل الدخول", },};export default function CardRtl() { const [mode, setMode] = React.useState<"rtl" | "ltr">("rtl"); const t = content[mode]; return ( <div className="flex flex-col items-center gap-6"> <Button variant="ghost" size="sm" onClick={() => setMode((m) => (m === "rtl" ? "ltr" : "rtl"))} > Toggle dir ({mode}) </Button> <Card className="w-full max-w-sm" dir={t.dir}> <CardHeader> <CardTitle>{t.title}</CardTitle> <CardDescription>{t.description}</CardDescription> <CardAction> <Button variant="link">{t.signUp}</Button> </CardAction> </CardHeader> <CardContent> <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="m@example.com" /> </div> <div className="grid gap-2"> <Label htmlFor="password-rtl">{t.password}</Label> <Input id="password-rtl" type="password" /> </div> </div> </CardContent> <CardFooter> <Button type="submit" className="w-full"> {t.login} </Button> </CardFooter> </Card> </div> );}API Reference
Card
| Prop | Type | Default |
|---|---|---|
size | "default" | "sm" | "default" |
The card spacing can also be customized via the --card-spacing CSS variable, e.g. className="[--card-spacing:--spacing(6)]".
CardHeader, CardTitle, CardDescription, CardAction, CardContent, and
CardFooter each accept a className for further customization.