---
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"
---
<Card class="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 id="card-demo-login">
<div class="flex flex-col gap-6">
<div class="grid gap-2">
<Label for="email">Email</Label>
<Input id="email" type="email" placeholder="m@example.com" required />
</div>
<div class="grid gap-2">
<div class="flex items-center">
<Label for="password">Password</Label>
<a
href="#"
class="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 class="flex-col gap-2">
<Button type="submit" form="card-demo-login" class="w-full">
Login
</Button>
<Button variant="outline" class="w-full">
Login with Google
</Button>
</CardFooter>
</Card>Installation
npx shadcn@latest add @fulldev/card
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>
Polymorphic
Card and CardTitle are polymorphic via as. Both render a div by
default. A dl card holds dt and dd pairs, optionally wrapped in a div.
Keep other links and buttons out of a link card.
<Card as="article">...</Card>
<Card as="section" aria-labelledby="plan-title">
...
</Card>
<Card as="a" href="/pricing">
...
</Card>
<Card as="li">...</Card>
<Card as="dl">
<div>
<dt>Plan</dt>
<dd>Pro</dd>
</div>
</Card>
<CardTitle as="h3">Card Title</CardTitle>
Composition
Card
├── CardHeader
│ ├── CardTitle
│ ├── CardDescription
│ └── CardAction
├── CardContent
└── CardFooter
Size
Use size="sm" for a small card with tighter spacing.
- Choose a schedule (daily, or weekly).
- Send to channels or specific teammates.
- Include charts, tables, and key metrics.
---
import ChevronRightIcon from "@lucide/astro/icons/chevron-right"
import { Button } from "@/components/ui/button"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card"
const featureName = "Scheduled reports"
---
<Card size="sm" class="mx-auto w-full max-w-xs">
<CardHeader>
<CardTitle>{featureName}</CardTitle>
<CardDescription>Weekly snapshots. No more manual exports.</CardDescription>
</CardHeader>
<CardContent>
<ul class="grid gap-2 py-2 text-sm">
<li class="flex gap-2">
<ChevronRightIcon class="mt-0.5 size-4 shrink-0 text-muted-foreground" />
<span>Choose a schedule (daily, or weekly).</span>
</li>
<li class="flex gap-2">
<ChevronRightIcon class="mt-0.5 size-4 shrink-0 text-muted-foreground" />
<span>Send to channels or specific teammates.</span>
</li>
<li class="flex gap-2">
<ChevronRightIcon class="mt-0.5 size-4 shrink-0 text-muted-foreground" />
<span>Include charts, tables, and key metrics.</span>
</li>
</ul>
</CardContent>
<CardFooter class="flex-col gap-2">
<Button size="sm" class="w-full">
Set up scheduled reports
</Button>
<Button variant="outline" size="sm" class="w-full">
See what's new
</Button>
</CardFooter>
</Card>Spacing
Set the --card-spacing CSS variable to control the gap between sections and
the inset of card parts.
---
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",
},
]
const spacing = "4"
const selectedSpacing = spacingOptions.find(
(option) => option.value === spacing
)
---
<div
class="mx-auto grid w-full max-w-sm gap-4"
data-card-spacing={JSON.stringify(spacingOptions)}
>
<ToggleGroup
defaultValue={spacing}
variant="outline"
size="sm"
class="justify-center"
>
{spacingOptions.map((option) => (
<ToggleGroupItem value={option.value}>{option.label}</ToggleGroupItem>
))}
</ToggleGroup>
<Card class={selectedSpacing?.className} data-value={spacing}>
<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 id="card-spacing-login">
<div class="flex flex-col gap-6">
<div class="grid gap-2">
<Label for="email-spacing">Email</Label>
<Input
id="email-spacing"
type="email"
placeholder="m@example.com"
required
/>
</div>
<div class="grid gap-2">
<div class="flex items-center">
<Label for="password-spacing">Password</Label>
<a
href="#"
class="ml-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 class="flex-col gap-2">
<Button type="submit" form="card-spacing-login" class="w-full">
Login
</Button>
<Button variant="outline" class="w-full">
Login with Google
</Button>
</CardFooter>
</Card>
</div>
<script>
type SpacingOption = { className: string; value: string }
const initialize = () => {
for (const root of document.querySelectorAll<HTMLElement>(
"[data-card-spacing]"
)) {
if (root.hasAttribute("data-bound")) continue
root.setAttribute("data-bound", "")
const options = JSON.parse(root.dataset.cardSpacing!) as SpacingOption[]
const group = root.querySelector('[data-slot="toggle-group"]')!
const card = root.querySelector<HTMLElement>('[data-slot="card"]')!
group.addEventListener("toggle-group:change", (event) => {
const [value] = (event as CustomEvent<{ value: string[] }>).detail.value
// Keep one spacing selected, as the controlled React example does.
if (!value) {
group.dispatchEvent(
new CustomEvent("toggle-group:set", {
detail: { value: card.dataset.value },
})
)
return
}
for (const option of options) card.classList.remove(option.className)
const selected = options.find((option) => option.value === value)
if (selected) card.classList.add(selected.className)
card.dataset.value = value
})
}
}
initialize()
document.addEventListener("astro:page-load", initialize)
</script>Use -mx-(--card-spacing) to make content go edge to edge, and
-mb-(--card-spacing) on CardContent when that content sits above a footer.
These terms govern your use of the workspace, including access to shared documents, project files, and collaboration tools.
You are responsible for the content you upload and for ensuring that your team has the appropriate permissions to view or edit it.
We may update features or limits as the service evolves. When those changes materially affect your workflow, we will notify your workspace administrators.
By continuing, you agree to keep your account credentials secure and to follow your organization's acceptable use policies.
---
import { Button } from "@/components/ui/button"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card"
---
<Card class="mx-auto w-full max-w-sm">
<CardHeader>
<CardTitle>Terms of Service</CardTitle>
<CardDescription>
Review the terms before accepting the agreement.
</CardDescription>
</CardHeader>
<CardContent class="-mb-(--card-spacing)">
<div class="-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 class="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 "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import {
Card,
CardAction,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card"
---
<Card class="relative mx-auto w-full max-w-sm pt-0">
<div class="absolute inset-0 z-30 aspect-video bg-black/35"></div>
<img
src="https://avatar.vercel.sh/shadcn1"
alt="Event cover"
class="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 class="w-full">View Event</Button>
</CardFooter>
</Card>RTL
---
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 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",
},
},
}
const { dir, values: t } = translations.ar
---
<Card class="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 id="card-rtl-login">
<div class="flex flex-col gap-6">
<div class="grid gap-2">
<Label for="email-rtl">{t.email}</Label>
<Input
id="email-rtl"
type="email"
placeholder={t.emailPlaceholder}
required
/>
</div>
<div class="grid gap-2">
<div class="flex items-center">
<Label for="password-rtl">{t.password}</Label>
<a
href="#"
class="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 class="flex-col gap-2">
<Button type="submit" form="card-rtl-login" class="w-full">
{t.login}
</Button>
<Button variant="outline" class="w-full">
{t.loginWithGoogle}
</Button>
</CardFooter>
</Card>API Reference
Source: src/components/ui/card
Card
| Prop | Type | Default |
|---|---|---|
size | "default" | "sm" | "default" |
as | HTML tag | "div" |
CardTitle
| Prop | Type | Default |
|---|---|---|
as | HTML tag | "div" |
CardHeader, CardDescription, CardAction, CardContent and CardFooter
render a div and take its attributes.