Card
@anxndsgn/ink-uiCard
A container for grouping related content and actions.
Project Update
Deploy your new project in one click.
Your project is ready for deployment. Review the settings and click deploy to go live.
import { Card, CardBody, CardDescription, CardFooter, CardHeader, CardTitle } from "@registry/components/ui/card";
import { Button } from "@registry/components/ui/button";
export function CardExample() {
return (
<Card className="w-full max-w-lg">
<CardHeader>
<CardTitle>Project Update</CardTitle>
<CardDescription>Deploy your new project in one click.</CardDescription>
</CardHeader>
<CardBody>
<p className="text-sm text-gray-600 dark:text-gray-400">
Your project is ready for deployment. Review the settings and click deploy to go live.
</p>
</CardBody>
<CardFooter>
<Button variant="secondary">Cancel</Button>
<Button>Deploy</Button>
</CardFooter>
</Card>
);
} Examples
With action
Use CardAction to place an action button in the header.
Notifications
Manage your notification preferences.
You have 3 unread notifications. Review them to stay up to date.
import { Card, CardAction, CardBody, CardDescription, CardHeader, CardTitle } from "@registry/components/ui/card";
import { Button } from "@registry/components/ui/button";
export function CardWithActionExample() {
return (
<Card className="w-full max-w-lg">
<CardHeader>
<CardTitle>Notifications</CardTitle>
<CardDescription>Manage your notification preferences.</CardDescription>
<CardAction>
<Button variant="ghost" size="sm">
Mark all read
</Button>
</CardAction>
</CardHeader>
<CardBody>
<p className="text-sm text-gray-600 dark:text-gray-400">
You have 3 unread notifications. Review them to stay up to date.
</p>
</CardBody>
</Card>
);
} Column footer
Use direction="column" on CardFooter to stack buttons vertically.
Subscribe
Get the latest updates delivered to your inbox.
Join our newsletter for weekly insights and product updates.
import { Card, CardBody, CardDescription, CardFooter, CardHeader, CardTitle } from "@registry/components/ui/card";
import { Button } from "@registry/components/ui/button";
export function CardColumnFooterExample() {
return (
<Card className="w-full max-w-lg">
<CardHeader>
<CardTitle>Subscribe</CardTitle>
<CardDescription>Get the latest updates delivered to your inbox.</CardDescription>
</CardHeader>
<CardBody>
<p className="text-sm text-gray-600 dark:text-gray-400">
Join our newsletter for weekly insights and product updates.
</p>
</CardBody>
<CardFooter direction="column">
<Button className="w-full">Subscribe Now</Button>
<Button variant="secondary" className="w-full">
Learn More
</Button>
</CardFooter>
</Card>
);
} Motion footer
You can wrap the footer in motion.div with AnimatePresence to animate it in and out when conditions change.
Profile Settings
Update your profile information.
import { Card, CardBody, CardDescription, CardFooter, CardHeader, CardTitle } from "@registry/components/ui/card";
import { Button } from "@registry/components/ui/button";
import { AnimatePresence, motion } from "motion/react";
import { useState } from "react";
import { Input } from "@registry/components/ui/input";
export function CardMotionFooterExample() {
const [name, setName] = useState("Alex Doe");
const [originalName] = useState("Alex Doe");
const hasChanges = name !== originalName;
return (
<Card className="w-full max-w-lg">
<CardHeader>
<CardTitle>Profile Settings</CardTitle>
<CardDescription>Update your profile information.</CardDescription>
</CardHeader>
<CardBody className="grid gap-4">
<div className="grid gap-2">
<label className="text-sm font-medium" htmlFor="name">
Name
</label>
<Input id="name" onChange={(e) => setName(e.target.value)} value={name} />
</div>
</CardBody>
<AnimatePresence>
{hasChanges && (
<motion.div
animate={{ height: "auto", opacity: 1 }}
className="grid min-h-0 overflow-hidden"
exit={{ height: "0px", opacity: 0 }}
initial={{ height: "0px", opacity: 0 }}
key="save-button"
transition={{
duration: 0.3,
ease: [0.79, 0.14, 0.15, 0.86],
}}
>
<CardFooter>
<AnimatePresence mode="wait">
<motion.div
animate={{ filter: "blur(0px)", opacity: 1, scale: 1 }}
exit={{ filter: "blur(6px)", opacity: 0, scale: 0.95 }}
initial={{ filter: "blur(6px)", opacity: 0, scale: 0.95 }}
key="reset-button"
transition={{
duration: 0.3,
ease: [0.79, 0.14, 0.15, 0.86],
}}
>
<Button onClick={() => setName(originalName)} type="button" variant="ghost">
Reset
</Button>
</motion.div>
</AnimatePresence>
<AnimatePresence mode="wait">
<motion.div
animate={{ filter: "blur(0px)", opacity: 1, scale: 1 }}
exit={{ filter: "blur(6px)", opacity: 0, scale: 0.95 }}
initial={{ filter: "blur(6px)", opacity: 0, scale: 0.95 }}
key="save-button"
transition={{
duration: 0.3,
ease: [0.79, 0.14, 0.15, 0.86],
}}
>
<Button type="submit">Save Changes</Button>
</motion.div>
</AnimatePresence>
</CardFooter>
</motion.div>
)}
</AnimatePresence>
</Card>
);
} Installation
Copy the source code below into your project:
import { mergeProps, useRender } from "@base-ui/react";
import { cn } from "@registry/lib/utils";
function Card({ className, ...props }: useRender.ComponentProps<"div">) {
const cardElement = useRender({
defaultTagName: "div",
props: {
...mergeProps<"div">(props, {
className: cn(
"relative flex flex-col rounded-3xl border border-transparent bg-card p-1 dark:border-border",
className,
),
}),
"data-slot": "card",
},
});
return cardElement;
}
function CardHeader({ className, ...props }: useRender.ComponentProps<"div">) {
const cardHeaderElement = useRender({
defaultTagName: "div",
props: {
...mergeProps<"div">(props, {
className: cn(
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 p-5 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
className,
),
}),
"data-slot": "card-header",
},
});
return cardHeaderElement;
}
function CardTitle({ className, ...props }: useRender.ComponentProps<"div">) {
const cardTitleElement = useRender({
defaultTagName: "div",
props: {
...mergeProps<"div">(props, {
className: cn("text-lg leading-none font-semibold", className),
}),
"data-slot": "card-title",
},
});
return cardTitleElement;
}
function CardDescription({ className, ...props }: useRender.ComponentProps<"div">) {
const cardDescriptionElement = useRender({
defaultTagName: "div",
props: {
...mergeProps<"div">(props, {
className: cn("text-sm text-muted-foreground", className),
}),
"data-slot": "card-description",
},
});
return cardDescriptionElement;
}
function CardAction({ className, ...props }: useRender.ComponentProps<"div">) {
const cardActionElement = useRender({
defaultTagName: "div",
props: {
...mergeProps<"div">(props, {
className: cn("col-start-2 row-span-2 row-start-1 self-start justify-self-end", className),
}),
"data-slot": "card-action",
},
});
return cardActionElement;
}
function CardPanel({ className, children, ...props }: useRender.ComponentProps<"div">) {
return (
<div
className={cn(
"w-full rounded-[calc(var(--radius-3xl)-var(--spacing))] border border-border bg-popover p-5",
className,
)}
data-slot="card-panel"
{...props}
>
{children}
</div>
);
}
function CardFooter({
className,
direction = "row",
...props
}: useRender.ComponentProps<"div"> & { direction?: "row" | "column" }) {
const cardFooterElement = useRender({
defaultTagName: "div",
props: {
...mergeProps<"div">(props, {
className: cn(
"flex items-center gap-2 p-5 [.border-t]:pt-5",
direction === "row" && "flex-row justify-end",
direction === "column" && "flex-col",
className,
),
}),
"data-slot": "card-footer",
},
});
return cardFooterElement;
}
export {
Card,
CardHeader,
CardFooter,
CardTitle,
CardAction,
CardDescription,
CardPanel,
CardPanel as CardContent,
CardPanel as CardBody,
}; API Reference
Card
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | — | Additional CSS classes to apply to the card container. |
CardHeader
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | — | Additional CSS classes to apply to the header container. |
CardTitle
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | — | Additional CSS classes to apply to the title element. |
CardDescription
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | — | Additional CSS classes to apply to the description element. |
CardAction
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | — | Additional CSS classes to apply to the action container. |
CardBody
CardBody is an alias for CardPanel.
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | — | Additional CSS classes to apply to the panel container. |
CardFooter
| Prop | Type | Default | Description |
|---|---|---|---|
| direction | "row" | "column" | "row" | Layout direction for the footer content. |
| className | string | — | Additional CSS classes to apply to the footer container. |