Group forms, summaries, or media on a bordered surface. Compose the sections you need and add CardAction for a control in the header.
import { Button } from "@/components/ui/button"import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle,} from "@/components/ui/card"export function Example() { return ( <Card className="w-full max-w-sm"> <CardHeader> <CardTitle>Welcome back</CardTitle> <CardDescription> Sign in with your work email to continue. </CardDescription> </CardHeader> <CardContent> <form className="flex flex-col gap-4"> <div className="flex flex-col gap-1.5"> <label htmlFor="email" className="text-sm"> Email </label> <input id="email" type="email" placeholder="you@company.com" className="h-9 w-full rounded-md border border-border bg-transparent px-3 text-sm outline-none" /> </div> <div className="flex flex-col gap-1.5"> <div className="flex items-center justify-between gap-2"> <label htmlFor="password" className="text-sm"> Password </label> <Button variant="link" className="text-xs text-muted-foreground"> Forgot password? </Button> </div> <input id="password" type="password" className="h-9 w-full rounded-md border border-border bg-transparent px-3 text-sm outline-none" /> </div> </form> </CardContent> <CardFooter className="flex-col gap-3"> <Button className="w-full">Continue</Button> <div className="flex w-full items-center gap-3 text-xs text-muted-foreground"> <span className="h-px flex-1 bg-border" /> or <span className="h-px flex-1 bg-border" /> </div> <Button variant="outline" className="w-full"> Continue with Google </Button> </CardFooter> </Card> )}Installation
npx @arctis-sh/@arctis-sh/ui@latest add cardUsage
import { Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle,} from "@/components/ui/card"<Card> <CardHeader> <CardTitle>Project overview</CardTitle> <CardDescription>Last synced 2 hours ago</CardDescription> <CardAction>Edit</CardAction> </CardHeader> <CardContent> <p>12 components published in this namespace.</p> </CardContent> <CardFooter> <p>Updated Jul 30</p> </CardFooter></Card>Composition
Card ├── CardHeader │ ├── CardTitle │ ├── CardDescription │ └── CardAction ├── CardContent └── CardFooter
Size
Pass size="sm" when the card needs tighter spacing.
import { Button } from "@/components/ui/button"import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle,} from "@/components/ui/card"export function Example() { return ( <Card size="sm" className="w-full max-w-sm"> <CardHeader> <CardTitle>Scheduled reports</CardTitle> <CardDescription> Weekly snapshots. No more manual exports. </CardDescription> </CardHeader> <CardContent> <ul className="list-disc space-y-1 pl-4 text-sm text-muted-foreground"> <li>Choose a schedule (daily, or weekly).</li> <li>Send to channels or specific teammates.</li> <li>Include charts, tables, and key metrics.</li> </ul> </CardContent> <CardFooter className="flex-col gap-2"> <Button size="sm" className="w-full"> Set up scheduled reports </Button> <Button size="sm" variant="outline" className="w-full"> See what's new <ChevronRightIcon data-icon="inline-end" /> </Button> </CardFooter> </Card> )}Spacing
Insets and section gaps use --card-spacing. Override it on the card to adjust the density in one place.
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card"export function Example() { return ( <Card className="w-full max-w-sm [--card-spacing:1.5rem]"> <CardHeader> <CardTitle>Card Title</CardTitle> </CardHeader> <CardContent>Content uses the shared spacing token.</CardContent> </Card> )}Use -mx-[var(--card-spacing)] to extend content to the card edges while keeping the surrounding inset aligned. If the block sits above a footer, add -mb-[var(--card-spacing)] to CardContent to remove the section gap.
import { Button } from "@/components/ui/button"import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle,} from "@/components/ui/card"export function Example() { return ( <Card className="w-full max-w-sm"> <CardHeader> <CardTitle>Terms of Service</CardTitle> <CardDescription> Review the terms before accepting the agreement. </CardDescription> </CardHeader> <CardContent className="-mx-[var(--card-spacing)] -mb-[var(--card-spacing)] border-y border-border p-0"> <div className="show-scrollbar max-h-40 space-y-3 overflow-y-auto px-[var(--card-spacing)] py-3 text-sm text-muted-foreground"> <p>These terms govern your use of the workspace…</p> <p>You are responsible for the content you upload…</p> <p>We may update features or limits as the service evolves…</p> </div> </CardContent> <CardFooter className="justify-end gap-2"> <Button variant="outline">Decline</Button> <Button>Accept</Button> </CardFooter> </Card> )}Image
Place media as the first child of Card, then add pt-0 so it sits flush with the top edge.
import { Badge } from "@/components/ui/badge"import { Button } from "@/components/ui/button"import { Card, CardDescription, CardFooter, CardHeader, CardTitle,} from "@/components/ui/card"export function Example() { return ( <Card className="relative w-full max-w-sm pt-0"> <img src="/assets/brand/demos/card-cover.png" alt="Event cover" className="aspect-video w-full object-cover" /> <Badge className="absolute top-3 right-3">Featured</Badge> <CardHeader> <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> )}Action
Put a header control in CardAction to align it to the top-right.
import { Button } from "@/components/ui/button"import { Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle,} from "@/components/ui/card"export function Example() { return ( <Card className="w-full max-w-sm"> <CardHeader> <CardTitle>Create an account</CardTitle> <CardDescription> Enter your email to get started with the workspace. </CardDescription> <CardAction> <Button variant="ghost" size="sm"> Sign in </Button> </CardAction> </CardHeader> <CardContent> <form className="flex flex-col gap-4"> <div className="flex flex-col gap-1.5"> <label htmlFor="name" className="text-sm"> Name </label> <input id="name" type="text" placeholder="Alex Rivera" className="h-9 w-full rounded-md border border-border bg-transparent px-3 text-sm outline-none" /> </div> <div className="flex flex-col gap-1.5"> <label htmlFor="email" className="text-sm"> Email </label> <input id="email" type="email" placeholder="you@company.com" className="h-9 w-full rounded-md border border-border bg-transparent px-3 text-sm outline-none" /> </div> </form> </CardContent> <CardFooter> <Button className="w-full">Create account</Button> </CardFooter> </Card> )}Split
Set the card to flex-row for a side-by-side media layout.
import { Button } from "@/components/ui/button"import { Card, CardDescription, CardFooter, CardHeader, CardTitle,} from "@/components/ui/card"export function Example() { return ( <Card className="w-full max-w-md flex-row gap-0 py-0"> <img src="/assets/brand/demos/card-cover.png" alt="" className="w-28 shrink-0 object-cover sm:w-36" /> <div className="flex min-w-0 flex-1 flex-col gap-[var(--card-spacing)] py-[var(--card-spacing)]"> <CardHeader> <CardTitle>Coastal workshop</CardTitle> <CardDescription> A half-day session on shipping design systems with less friction. </CardDescription> </CardHeader> <CardFooter> <Button size="sm">Register</Button> </CardFooter> </div> </Card> )}Empty
A dashed border and bg-transparent keep an empty state visually light.
import { Button } from "@/components/ui/button"import { Card, CardContent, CardDescription, CardHeader, CardTitle,} from "@/components/ui/card"export function Example() { return ( <Card className="w-full max-w-sm border-dashed bg-transparent"> <CardHeader> <CardTitle>No projects yet</CardTitle> <CardDescription> Create a project to start collecting feedback and shipping updates. </CardDescription> </CardHeader> <CardContent className="flex justify-end"> <Button size="sm"> Create project </Button> </CardContent> </Card> )}Danger
Pair destructive footer actions with direct copy that explains the consequence.
import { Button } from "@/components/ui/button"import { Card, CardDescription, CardFooter, CardHeader, CardTitle,} from "@/components/ui/card"export function Example() { return ( <Card className="w-full max-w-sm"> <CardHeader> <CardTitle>Delete workspace</CardTitle> <CardDescription> This permanently removes projects, members, and billing history. This cannot be undone. </CardDescription> </CardHeader> <CardFooter className="justify-end gap-2"> <Button variant="outline" size="sm">Cancel</Button> <Button variant="destructive" size="sm">Delete workspace</Button> </CardFooter> </Card> )}API Reference
Card
| Prop | Type | Default |
|---|---|---|
| size | "default" | "sm" | "default" |
| className | string | — |
<Card> <CardHeader> <CardTitle>Title</CardTitle> <CardDescription>Subtitle</CardDescription> </CardHeader> <CardContent>Body</CardContent></Card>CardHeader
| Prop | Type | Default |
|---|---|---|
| className | string | — |
<CardHeader> <CardTitle>Title</CardTitle> <CardDescription>Subtitle</CardDescription></CardHeader>CardTitle
| Prop | Type | Default |
|---|---|---|
| className | string | — |
<CardTitle>Title</CardTitle>CardDescription
| Prop | Type | Default |
|---|---|---|
| className | string | — |
<CardDescription>Subtitle</CardDescription>CardAction
Places content in the top-right corner of the header.
| Prop | Type | Default |
|---|---|---|
| className | string | — |
<CardHeader> <CardTitle>Title</CardTitle> <CardAction> <Button size="sm" variant="outline">Edit</Button> </CardAction></CardHeader>CardContent
| Prop | Type | Default |
|---|---|---|
| className | string | — |
<CardContent>Body</CardContent>CardFooter
| Prop | Type | Default |
|---|---|---|
| className | string | — |
<CardFooter> <Button>Save</Button></CardFooter>