Card
A content container with 4 surface variants and 3 inset sizes, composed from header, title, description, content and footer parts.
import { Button } from "@/components/ui/button"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card"
export default function CardDemo() {
return (
<Card variant="elevated" className="max-w-sm">
<CardHeader>
<CardTitle>Production</CardTitle>
<CardDescription>Deployed 12 minutes ago</CardDescription>
</CardHeader>
<CardContent>
<p className="text-sm text-muted-foreground">
4.2M requests this month across 3 regions.
</p>
</CardContent>
<CardFooter>
<Button size="sm" tone="brand">
View logs
</Button>
</CardFooter>
</Card>
)
}Installation
Run the following command
npx shadcn@latest add @ai2/cardDependencies, the @ai2/tokens theme and the component file are installed together. @ai2/glass installs alongside, since the glass variant reuses its overlay treatment.
Install dependencies
npm install class-variance-authority@^0.7.1Add the cn util
lib/utils.tsimport { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
/* Adds a source-attribution ref param to a URL (the inspiration exports mark their
outbound links with an ai2.design attribution). An invalid URL is returned as is.
This file is SHOWN TO THE CONSUMER: the docs component pages render the source of
`cn` in a code block, so a Turkish comment here would reach every one of those
pages. Keep it English. */
export function withRef(url: string, ref = "ai2.design"): string {
try {
const u = new URL(url)
u.searchParams.set("ref", ref)
return u.toString()
} catch {
return url
}
}Copy the source code
components/ui/card.tsximport type * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
import { glass } from "@/components/ui/glass"
const cardVariants = cva("flex flex-col text-card-foreground", {
variants: {
variant: {
outline: "rounded-xl border bg-card",
elevated:
"rounded-xl border border-transparent bg-card shadow-md shadow-black/5 dark:border-border dark:shadow-black/20",
soft: "rounded-xl bg-surface-3 dark:bg-surface-2",
glass: `rounded-xl border border-white/20 dark:border-white/10 ${glass.overlay}`,
},
inset: {
sm: "gap-4 py-4 [&_[data-slot=card-content]]:px-4 [&_[data-slot=card-footer]]:px-4 [&_[data-slot=card-header]]:px-4",
md: "gap-6 py-6 [&_[data-slot=card-content]]:px-6 [&_[data-slot=card-footer]]:px-6 [&_[data-slot=card-header]]:px-6",
lg: "gap-8 py-8 [&_[data-slot=card-content]]:px-8 [&_[data-slot=card-footer]]:px-8 [&_[data-slot=card-header]]:px-8",
},
},
defaultVariants: {
variant: "outline",
inset: "md",
},
})
interface CardProps
extends React.ComponentProps<"div">,
VariantProps<typeof cardVariants> {}
function Card({ className, variant, inset, ...props }: CardProps) {
return (
<div
data-slot="card"
data-variant={variant ?? "outline"}
className={cn(cardVariants({ variant, inset, className }))}
{...props}
/>
)
}
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-header"
className={cn("flex flex-col gap-1.5", 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 CardContent({ className, ...props }: React.ComponentProps<"div">) {
return <div data-slot="card-content" className={cn(className)} {...props} />
}
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-footer"
className={cn("flex items-center gap-2", className)}
{...props}
/>
)
}
export {
Card,
CardHeader,
CardTitle,
CardDescription,
CardContent,
CardFooter,
cardVariants,
type CardProps,
}Card imports the glass helper from the Glass component, so copy that file too. Manual installs also skip the @ai2/tokens theme, so add the token CSS from the theming guide or the surface colors will be missing.
Usage
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card"
<Card variant="elevated">
<CardHeader>
<CardTitle>Production</CardTitle>
<CardDescription>Deployed 12 minutes ago</CardDescription>
</CardHeader>
<CardContent>4.2M requests this month.</CardContent>
<CardFooter>…</CardFooter>
</Card>Every part is optional. Compose only what you need. Header, content and footer pick up the card's inset padding automatically.
Examples
Variants
glass is designed to sit over imagery or gradients. On a plain background it reads close to outline.
Inset
Full anatomy
Props
The axes live on the root Card. CardHeader, CardTitle, CardDescription, CardContent and CardFooter are plain <div> wrappers and accept every native div prop.
| Prop | Type | Default | Description |
|---|---|---|---|
variant | "outline" | "elevated" | "soft" | "glass" | "outline" | Surface treatment: border, shadow, filled or translucent. |
inset | "sm" | "md" | "lg" | "md" | Padding density, applied to the card and inherited by header, content and footer. |
ai2 Card: a composable content container for React
The ai2 Card is a shadcn-compatible card component for React, styled with Tailwind CSS v4 on top of the shared ai2 token file. It is the workhorse surface of the system: dashboards, settings panels, pricing grids and empty states all start from the same six-part anatomy, with 4 surface variants and 3 inset densities controlled from the root.
Because it ships through the shadcn registry format, you install it with one CLI command, an MCP agent, or a copy-paste, and the source lands in your own project. You own the file; there is no runtime dependency on ai2 itself. The live example above is the exact component you get.
What is the ai2 Card?
It is a six-part composition: Card, CardHeader, CardTitle, CardDescription, CardContent and CardFooter. The anatomy matches shadcn/ui, so existing snippets, muscle memory and AI agents keep working, while the root adds two cva axes on top.
The root exposes variant (outline, elevated, soft, glass) and inset (sm, md, lg). The inset value cascades: header, content and footer read the card's padding through data-slot selectors, so density changes in one place. Every part is a plain div, which keeps the card server-safe and free of client-side JavaScript.
Why use it
- Four surface variants: outline for the classic bordered card, elevated for a soft shadow, soft for a filled borderless surface, and glass for a translucent backdrop-blur panel over imagery.
- Density as a prop: inset="sm", "md" or "lg" sets padding and gap for the whole card at once; header, content and footer inherit it automatically.
- Compose only what you need: Every part is optional. A stat tile can be Card plus CardContent; a settings block can use the full header, content and footer stack.
- Server component friendly: The card is plain divs with no "use client" directive and no state, so it renders in React Server Components without shipping JavaScript.
- Agent-readable metadata: The registry item describes the variant matrix and intended use in plain words, so an MCP agent can find, inspect and install it without guessing.
Features
- shadcn registry install: One command adds the component and the @ai2/tokens theme to your project.
- Glass variant: Derived from the shared @ai2/glass overlay treatment: a token-driven translucent background with backdrop blur and inset highlights, tuned for both light and dark mode and designed to sit over gradients and imagery.
- Theme-aware shadows: The elevated variant uses a subtle shadow in light mode and restores a visible border in dark mode, where shadows read poorly.
- Data attributes for styling: The root exposes data-slot="card" and data-variant; every part has its own data-slot, so CSS overrides can target exact regions.
- Exported cardVariants: The cva function is exported, so you can apply the same surface treatment to other elements, for example a link styled as a card.
- TypeScript source: CardProps extends the native div props plus the cva axes, so autocomplete covers the full API in your editor.
Production tips
- Match inset to context: Dense dashboards and sidebars read best with inset="sm"; the md default fits most product UI; reserve lg for marketing panels and empty states.
- Use glass over imagery: The glass variant depends on what is behind it. On a plain background it reads close to outline, so pair it with a gradient, photo or chart.
- Keep heading semantics yourself: CardTitle is a styled div, not an h-tag. If the card title matters for the document outline, render an h2 or h3 inside it.
- Put actions in the footer: CardFooter is a flex row with a small gap, made for buttons. Keeping actions there gives every card in a grid the same visual rhythm.
- Watch nested surfaces: A soft card inside another soft card can lose contrast. Alternate variants, for example outline inside soft, when you nest surfaces.
Works with the rest of ai2
The card is a natural host for the rest of the registry. Put an ai2 Avatar and an ai2 Badge in the header for user or status cards, fill the content with an ai2 Table for compact data views, and finish the footer with ai2 Button actions.
For loading states, mirror the card's layout with ai2 Skeleton blocks so content swaps in without shift. Everything shares one token source, so any combination stays consistent in both light and dark mode.