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 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> <div class="flex flex-col gap-6"> <div class="grid gap-2"> <Label for="card-demo-email">Email</Label> <Input id="card-demo-email" type="email" placeholder="m@example.com" required /> </div> <div class="grid gap-2"> <div class="flex items-center"> <Label for="card-demo-password">Password</Label> <a href="/docs" class="ml-auto inline-block text-sm underline-offset-4 hover:underline" > Forgot your password? </a> </div> <Input id="card-demo-password" type="password" required /> </div> </div> </form> </CardContent> <CardFooter class="flex-col gap-2"> <Button type="submit" class="w-full"> Login </Button> <Button variant="outline" class="w-full"> Login with Google </Button> </CardFooter> </Card> );}Installation
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└── CardFooterSize
Use the size="sm" prop to set the card to its compact size.
- Choose a schedule (daily, or weekly).
- Send to channels or specific teammates.
- Include charts, tables, and key metrics.
import { ChevronRight } from "lucide-solid";import { Button } from "~/components/ui/button";import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle,} from "~/components/ui/card";
export default function CardSmall() { const featureName = "Scheduled reports";
return ( <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"> <ChevronRight 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"> <ChevronRight 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"> <ChevronRight 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
Use the --card-spacing CSS variable to control the gap between sections and the inset of card parts.
import { createMemo, createSignal, For } from "solid-js";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 = [ { class: "[--card-spacing:--spacing(4)]", label: "16px", value: "4", }, { class: "[--card-spacing:--spacing(5)]", label: "20px", value: "5", }, { class: "[--card-spacing:--spacing(6)]", label: "24px", value: "6", }, { class: "[--card-spacing:--spacing(8)]", label: "32px", value: "8", },];
export default function CardSpacing() { const [spacing, setSpacing] = createSignal("4"); const selectedSpacing = createMemo(() => spacingOptions.find((option) => option.value === spacing()), );
return ( <div class="mx-auto grid w-full max-w-sm gap-4"> <ToggleGroup value={spacing()} onChange={(value) => setSpacing(value ?? "4")} variant="outline" size="sm" class="justify-center" > <For each={spacingOptions}> {(option) => <ToggleGroupItem value={option.value}>{option.label}</ToggleGroupItem>} </For> </ToggleGroup> <Card class={selectedSpacing()?.class}> <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 class="flex flex-col gap-6"> <div class="grid gap-2"> <Label for="card-spacing-email">Email</Label> <Input id="card-spacing-email" type="email" placeholder="m@example.com" required /> </div> <div class="grid gap-2"> <div class="flex items-center"> <Label for="card-spacing-password">Password</Label> <a href="/docs" class="ml-auto inline-block text-sm underline-offset-4 hover:underline" > Forgot your password? </a> </div> <Input id="card-spacing-password" type="password" required /> </div> </div> </form> </CardContent> <CardFooter class="flex-col gap-2"> <Button type="submit" class="w-full"> Login </Button> <Button variant="outline" class="w-full"> Login with Google </Button> </CardFooter> </Card> </div> );}Use negative margins with -mx-(--card-spacing) to make content edge-to-edge while keeping it aligned with the card inset.
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";
export default function CardEdgeToEdge() { return ( <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 edge-to-edge 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 class="relative mx-auto w-full max-w-sm pt-0"> <div class="absolute inset-0 z-30 aspect-video bg-black/35" /> <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> );}API Reference
Card
The root container for card content.
CardHeader
Contains a title, description, and optional action.
CardTitle
The card title.
CardDescription
Helper text displayed under the title.
CardAction
Places content in the top-right of the header.
CardContent
The main card body.
CardFooter
Actions or secondary content displayed at the bottom of the card.
Changelog
Spacing Variable
If you're upgrading from a previous version of the Card component, apply the following updates to use the --card-spacing variable.
Update the Card root spacing classes.
Replace the hard-coded gap and vertical padding with --card-spacing, and set the default and small size values on the root:
gap-6 py-6 data-[size=sm]:gap-4 data-[size=sm]:py-4gap-(--card-spacing) py-(--card-spacing) [--card-spacing:--spacing(6)] data-[size=sm]:[--card-spacing:--spacing(4)]Update CardHeader spacing classes.
Replace the horizontal padding and border spacing with the shared variable:
px-6 group-data-[size=sm]/card:px-4 [.border-b]:pb-6 group-data-[size=sm]/card:[.border-b]:pb-4px-(--card-spacing) [.border-b]:pb-(--card-spacing)Update CardContent and CardFooter spacing classes.
Use --card-spacing for the content inset and footer padding:
px-6 group-data-[size=sm]/card:px-4px-(--card-spacing)
px-6 group-data-[size=sm]/card:px-4 [.border-t]:pt-6 group-data-[size=sm]/card:[.border-t]:pt-4px-(--card-spacing) [.border-t]:pt-(--card-spacing)After applying these changes, customize card spacing by setting --card-spacing on the Card with an arbitrary property class:
function Example() { return <Card class="[--card-spacing:--spacing(6)]">...</Card>;}