Card
SaveA hairline surface for grouped content, with header, content, and footer parts and an optional interactive state.
- Category
- Core
- Version
- 0.2.0
- Frameworks
- Next.js, Vite + React
- Styling
- Tailwind CSS v4
Preview
Card preview
Research agent
Answers questions from your docs and cites its sources.
- Runs
- 1,284
- Success
- 97.2%
- Median
- 4.1 s
Updated 2 min agoView runs
Installation
npx @seamlessui/cli add cardPre-release@seamlessui/cli is not on npm yet. Install from a local build.
Review the plan first. A dry run lists every file and package without changing anything:
npx @seamlessui/cli add card --dry-runUsage
card-example.tsx
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/seamless/ui/card";
const stats = [
{ label: "Runs", value: "1,284" },
{ label: "Success", value: "97.2%" },
{ label: "Median", value: "4.1 s" },
];
export default function CardExample() {
return (
<Card interactive className="w-full max-w-sm">
<CardHeader>
<CardTitle>Research agent</CardTitle>
<CardDescription>Answers questions from your docs and cites its sources.</CardDescription>
</CardHeader>
<CardContent>
<dl className="grid grid-cols-3 divide-x divide-zinc-950/[0.07] rounded-lg border border-zinc-950/[0.07] dark:divide-white/[0.08] dark:border-white/[0.08]">
{stats.map((stat) => (
<div key={stat.label} className="grid gap-1 px-3 py-2.5">
<dt className="text-xs font-medium text-zinc-500">{stat.label}</dt>
<dd className="font-mono text-sm text-zinc-950 tabular-nums dark:text-white">{stat.value}</dd>
</div>
))}
</dl>
</CardContent>
<CardFooter className="justify-between">
<span className="text-xs text-zinc-500">Updated 2 min ago</span>
<a
href="#runs"
className="group inline-flex items-center gap-1 rounded-md text-sm font-medium text-zinc-950 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-sky-500 dark:text-white"
>
View runs
<svg
aria-hidden="true"
viewBox="0 0 16 16"
fill="none"
className="size-4 text-zinc-500 transition-[translate,color] duration-150 ease-out group-hover:text-current motion-safe:group-hover:translate-x-0.5 motion-reduce:transition-none"
>
<path d="M6 4l4 4-4 4" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</a>
</CardFooter>
</Card>
);
}API
| Prop | Type | Default | Description |
|---|---|---|---|
| interactive | boolean | false | Strengthens the border and shadow on hover and when something inside has keyboard focus. Use when the card contains the primary link or action. |
| CardHeader, CardTitle, CardDescription, CardContent, CardFooter | ComponentProps<…> | — | Layout parts. Each accepts the props of its element (div, h3, or p). |
Accessibility
- CardTitle renders an <h3>; pass a different element with your own heading if the page outline needs it.
- The interactive state also shows on keyboard focus inside the card (has-focus-visible). It only changes border and shadow color, so nothing moves; the transition is removed with reduced motion.
Files
Installed into src/components/seamless, or components/seamless when the project has no src directory.
src/components/seamless/ui/card.tsxcard- npm packages: noneReact and Tailwind CSS v4 required
Source
The exact source the CLI installs, from /r/items/react/card/0.2.0.json.
ui/card.tsxShowHide
ui/card.tsx
import type { ComponentProps } from "react";
function cx(...classes: (string | false | null | undefined)[]) {
return classes.filter(Boolean).join(" ");
}
export interface CardProps extends ComponentProps<"div"> {
/**
* For cards that act as links or buttons: the border and shadow strengthen on hover and
* when something inside has keyboard focus.
*/
interactive?: boolean;
}
export function Card({ interactive = false, className, ...props }: CardProps) {
return (
<div
data-interactive={interactive || undefined}
className={cx(
"rounded-xl border border-zinc-950/10 bg-white text-zinc-950 shadow-[0_1px_2px_rgb(0_0_0/0.04)]",
"dark:border-white/10 dark:bg-black dark:text-white dark:shadow-[inset_0_1px_0_rgb(255_255_255/0.06)]",
interactive &&
cx(
"transition-[border-color,box-shadow] duration-150 ease-out motion-reduce:transition-none",
"hover:border-zinc-950/15 hover:shadow-[0_1px_2px_rgb(0_0_0/0.04),0_6px_16px_-8px_rgb(0_0_0/0.12)]",
"has-focus-visible:border-zinc-950/15",
"dark:hover:border-white/20 dark:hover:shadow-[inset_0_1px_0_rgb(255_255_255/0.08)] dark:has-focus-visible:border-white/20",
),
className,
)}
{...props}
/>
);
}
/** Title block. Holds a CardTitle and an optional CardDescription. */
export function CardHeader({ className, ...props }: ComponentProps<"div">) {
return <div className={cx("flex flex-col gap-1.5 px-5 pt-5", className)} {...props} />;
}
export function CardTitle({ className, ...props }: ComponentProps<"h3">) {
return <h3 className={cx("text-base leading-6 font-semibold tracking-[-0.01em]", className)} {...props} />;
}
export function CardDescription({ className, ...props }: ComponentProps<"p">) {
return <p className={cx("text-sm leading-relaxed text-zinc-600 dark:text-zinc-400", className)} {...props} />;
}
export function CardContent({ className, ...props }: ComponentProps<"div">) {
return <div className={cx("p-5", className)} {...props} />;
}
/** Action row, separated from the content by a divider. */
export function CardFooter({ className, ...props }: ComponentProps<"div">) {
return (
<div
className={cx(
"flex items-center gap-2 border-t border-zinc-950/[0.07] px-5 py-3 dark:border-white/[0.08]",
className,
)}
{...props}
/>
);
}