Card
A flexible surface container for grouping related content. Four variants (elevated/outlined/filled/ghost), five intents, six rounded options, five sizes, horizontal layout, interactive states, and LTR/RTL support.
Playground
Installation
pnpm add @tessinaui/uiUsage
import {
Card,
CardMedia,
CardHeader,
CardTitle,
CardDescription,
CardAction,
CardContent,
CardFooter,
CardDivider,
} from "@tessinaui/ui";{/* Basic card */}
<Card>
<CardHeader>
<CardTitle>Title</CardTitle>
<CardDescription>Description</CardDescription>
</CardHeader>
<CardContent>Content</CardContent>
<CardFooter>
<Button variant="primary">Action</Button>
</CardFooter>
</Card>
{/* Outlined with intent */}
<Card variant="outlined" intent="success">
<CardHeader>
<CardTitle>Success</CardTitle>
</CardHeader>
</Card>
{/* Card with media */}
<Card rounded="lg" className="overflow-hidden w-72">
<CardMedia src="/image.jpg" alt="Cover" aspectRatio="video" position="top" />
<CardHeader>
<CardTitle>Article title</CardTitle>
</CardHeader>
</Card>
{/* Interactive card */}
<Card interactive onClick={() => navigate('/details')}>
<CardContent>Click me</CardContent>
</Card>
{/* Horizontal layout — CardBody supplies the column's vertical rhythm.
Start/end media width defaults to the size-scaled --card-media-w
(capped at 40% so the text column never starves); override with any
w-* class. */}
<Card horizontal className="max-w-lg overflow-hidden">
<CardMedia position="start" className="bg-secondary" />
<CardBody>
<CardHeader>
<CardTitle>Horizontal card</CardTitle>
</CardHeader>
<CardContent>Content beside media</CardContent>
</CardBody>
</Card>
{/* RTL */}
<Card dir="rtl" variant="outlined">
<CardHeader>
<CardTitle>عنوان البطاقة</CardTitle>
</CardHeader>
</Card>Header action
CardAction claims the trailing slot of the header grid — top-aligned with the title, flipped to the start edge in RTL. Use it for link buttons, badges, or a dismiss IconButton.
<Card>
<CardHeader>
<CardTitle>Team members</CardTitle>
<CardDescription>Invite and manage collaborators.</CardDescription>
<CardAction>
<Button variant="ghost" size="xs">View all</Button>
</CardAction>
</CardHeader>
<CardContent>…</CardContent>
</Card>Edge-to-edge content
The root owns the spacing model — it sets --card-p (edge padding) and --card-gap (section rhythm) per size, while sections pad horizontally only — so any child can bleed to the card edge with -mx-(--card-p) at every size.
<Card>
<CardHeader>
<CardTitle>Monthly report</CardTitle>
</CardHeader>
<CardContent>
<div className="-mx-(--card-p) border-y border-border">
{/* full-bleed table, chart, or list rows */}
</div>
</CardContent>
</Card>Examples
Default
A basic card with header, content, and footer sections.
Variants
Four surface styles — elevated, outlined, filled, and ghost.
Intents
Semantic intent colors applied to the card surface.
Sizes
The size prop scales the card's --card-p / --card-gap spacing rhythm.
With Media
A CardMedia block rounds its top corners to sit flush with the card edge.
Loading
Use CardSkeleton to show a placeholder while content loads.
API Reference
Card Props
| Prop | Type | Default | Description |
|---|---|---|---|
variant | "elevated" | "outlined" | "filled" | "ghost" | "elevated" | Visual surface style |
intent | "none" | "error" | "warning" | "success" | "info" | "none" | Semantic intent color applied to the card surface |
size | "xs" | "sm" | "md" | "lg" | "xl" | "md" | Spacing scale — sets the card's --card-p / --card-gap rhythm |
rounded | "none" | "sm" | "md" | "lg" | "full" | "md" | Corner radius |
width | "narrow" | "default" | "wide" | "full" | "default" | Max-width constraint |
shadow | "none" | "sm" | "md" | "lg" | "md" for elevated, "none" otherwise | Drop shadow depth |
interactive | boolean | false | Adds hover / active / focus-ring states |
disabled | boolean | false | Dims the card and disables pointer events |
horizontal | boolean | false | Switches internal flex direction to row |
dir | "ltr" | "rtl" | "ltr" | Text direction |
className | string | — | Additional class on the root element |
CardMedia Props
| Prop | Type | Default | Description |
|---|---|---|---|
src | string | — | Image source URL |
alt | string | "" | Image alt text |
aspectRatio | "auto" | "video" | "4/3" | "1/1" | "2/1" | "3/4" | "video" | Aspect ratio of the media container |
position | "top" | "bottom" | "start" | "end" | "fill" | "top" | Edge the media abuts — determines which corners are rounded |
overlay | "none" | "scrim" | "gradient" | "none" | Legibility layer over the image — scrim darkens uniformly, gradient fades up from the bottom (caption-friendly) |
children | ReactNode | — | With src: floated over the image in the top-end corner (badges, chips). Without src: replaces the default <img> (video, iframe, custom media) |
CardHeader Props
| Prop | Type | Default | Description |
|---|---|---|---|
icon | ReactNode | — | Icon / illustration rendered above (or inline with) the title |
inlineIcon | boolean | false | Lays out icon and title text in a horizontal row |
CardAction
A plain div with no extra props — the trailing header slot. Render it inside CardHeader alongside CardTitle / CardDescription; the header grid places it in the end column, top-aligned with the title. Logical grid placement means it lands on the left automatically in RTL.
CardBody
A plain div with no extra props — the content column of a horizontal card (flex min-w-0 flex-1 flex-col gap-(--card-gap)). Wrap CardHeader / CardContent / CardFooter in it next to a start/end CardMedia; it reuses the inherited --card-gap so the column's rhythm matches a vertical card exactly.
CardFooter Props
| Prop | Type | Default | Description |
|---|---|---|---|
vertical | boolean | false | Stacks footer items vertically |
align | "start" | "center" | "end" | "between" | "start" | Alignment of footer items |
Variants
| Value | Style |
|---|---|
"elevated" | bg-card with drop shadow (default shadow-md) |
"outlined" | bg-background with border-border — flat with visible border |
"filled" | bg-secondary (or intent-light color) — tinted surface |
"ghost" | Transparent — no border, no shadow |
Notes
- Spacing model — the root owns two CSS variables per
size:--card-p(edge padding, applied as the root's vertical padding) and--card-gap(the flex gap between sections). Sections apply horizontal padding only (px-(--card-p)), so the space between any two sections is always exactly--card-gap— no double padding. Any child can bleed edge-to-edge with-mx-(--card-p), andCardMediacancels the root's vertical padding the same way.lg/xlautomatically tighten--card-pon mobile (max-md) so large cards stay comfortable on phones. - Machine-readable anatomy — every part exposes a
data-slotattribute (card,card-media,card-header,card-title,card-description,card-action,card-content,card-footer,card-divider) for styling hooks and tooling. - Accessible naming — interactive cards get
role="button"and are automatically labelled by theirCardTitleviaaria-labelledby, so assistive tech announces the card by its visible title. - CardMedia rounding — use
positionto control which corners are rounded on the media figure. Setposition="start"for the left edge of horizontal cards; it uses logical CSS properties so it flips automatically in RTL. - Interactive cards — when
interactiveis set and the card contains nested buttons or links, prefer the stretched-link pattern (absolute-positioned<a>) rather thanonClickon the card root to avoid nested interactive element accessibility issues. - Horizontal layout — set
horizontalon theCardroot, then useCardMediawithposition="start"(or"end") and wrap the remaining sections inCardBody, which restores the card's vertical rhythm inside the column (the root's gap only separates its direct children). Start/end media stretches the card's full height (aspectRatio="auto"default) and its width defaults to the size-scaled--card-media-w(5–12rem fromxstoxl, capped at 40% of the card) so it grows proportionally withsize— override with anyw-*class. - Width —
narrowandwideincludew-fullwith amax-wcap, so they engage even when the card is a flex item;defaultsizes to context. - RTL —
dir="rtl"is applied to the root and propagates to all children. Logical CSS properties (rounded-s-*,rounded-e-*) ensure media rounding flips correctly.
Calendar
Date picker calendar built on react-day-picker v9. Supports single, range, and multiple selection modes, RTL, week numbers, disabled dates, multi-month view, and an optional footer slot.
Carousel
Touch-friendly slider with multiple variants — slide, card/3D, fade, and free-scroll. Built on Embla Carousel with autoplay, loop, RTL, and vertical orientation support.