Card React component for Tailwind CSS v4
v1.0.0Added in 1.0.0
A React card component for listings, stats, settings and plans: media, header, body and footer parts, and a lift-and-press mode.
A plain card just holds content. An interactive card lifts 2px on hover with a deeper shadow while its photo zooms in a touch, and dips to 98% while pressed; pressing a button inside it (a like, a menu) presses only that button. Link cards are one Tab stop with the focus ring around the whole card: Enter opens them, a click anywhere follows the link, and the buttons on the photo, in the header and in the footer keep working. A card with onClick is a real button to the keyboard (Enter or Space). Selectable cards built on a radio group move with the arrow keys and show a ring in the tone. A disabled card dims and leaves the tab order. Phones get no hover lift, only the press, and horizontal cards stack their photo on top below 640px.
Card is a free React component in the React layout components category, written in TypeScript for React 19 and Tailwind CSS v4. It installs as source with npx wingo-ui@latest add card, together with Tones and UIProvider and the npm package @radix-ui/react-slot.
Add to a project
Requirements: React 19 and Tailwind CSS v4, in Next.js (App Router or Pages), Vite or React Router. Installation guide · Next.js setup · Vite setup
npx wingo-ui@latest add cardUsage
Import Card from @/components/ui/card.
import { Heart } from "lucide-react"
import { Avatar } from "@/components/ui/avatar"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { Card, CardFooter, CardLink, CardMedia } from "@/components/ui/card"
export function ListingCard({ onSave }: { onSave: () => void }) {
return (
<Card
className="max-w-sm"
media={
<CardMedia src="/demo/apartment.jpg">
<Badge variant="solid" size="sm" className="absolute start-3 top-3">New</Badge>
<Button size="icon-sm" variant="soft" radius="full" aria-label="Save" onClick={onSave} className="absolute end-3 top-3">
<Heart />
</Button>
</CardMedia>
}
title={<CardLink href="/listings/10482">2-bedroom apartment, Park Slope</CardLink>}
description="Brooklyn · 580 sq ft · floor 3 of 4"
footer={
<CardFooter align="between">
<span className="text-[17px] font-semibold">$445,000</span>
<span className="flex items-center gap-2 text-[13px] text-muted-foreground">
<Avatar src="/demo/ana-pop.jpg" name="Anna Price" size="xs" /> today, 2:20 PM
</span>
</CardFooter>
}
/>
)
}Props
titledefault""ReactNodeSimple API: the heading (an h3). Pass a CardLink here to make the whole card a link (interactive turns on by itself). The demo shows its sample listing title while empty.
descriptiondefault""ReactNodeSimple API: the muted line under the title. The demo shows its sample while empty.
variantdefault"elevated""elevated" | "outline" | "soft" | "ghost"elevated is a raised white panel (the surface token with its shadow), outline a hairline frame, soft the flat gray tile (tinted by a tone), ghost only padding, for lists of cards on a panel. Deviation: elevated is a card-only value.
tonedefault"neutral""neutral" | "brand" | "success" | "warning" | "danger" | "info"Tints soft and outline cards (the title takes the tone color) and colors the selected ring. Elevated and ghost cards only show it when selected.
colordefault""stringAny CSS color. Replaces the tone: a 14% tint on soft cards, the border on outline cards, the selected ring.
sizedefault"md""sm" | "md" | "lg"Padding 16 / 20 / 24px (md and lg use one step less on phones), 12 / 16 / 20px between parts, and the title size. Media bleeds and divided footers follow.
radiusdefault"md""none" | "sm" | "md" | "lg" | "full"Corner radius: 0, 12, 16 or 24px; full falls back to 24px. Inset media takes the radius minus the padding.
interactivedefaultfalsebooleanHover lift (2px and a floating shadow, a tint on ghost cards), a 0.98 press, pointer cursor and the focus ring; media zooms on hover. On by default with onClick, with a CardLink as the title prop, and with asChild onto a link, button or label. With the parts, set it next to a CardLink so the card really goes somewhere.
selecteddefaultfalsebooleanA ring in the tone around selectable cards (plans, addresses, payment methods). With onClick the card also reports aria-pressed.
disableddefaultfalsebooleanDims the card and blocks the pointer, the lift and the press. Link and button cards, and a CardLink inside, get aria-disabled, leave the tab order and ignore clicks; disable other controls inside it yourself.
orientationdefault"vertical""vertical" | "horizontal"horizontal puts the media at the start from 640px up, bleeding to the top, bottom and start edges; phones keep it on top. With parts, wrap the rest in CardContent.
divideddefaultfalsebooleanA hairline above the footer, edge to edge (stopping at start media). The default for every CardFooter inside, which can still override it.
asChilddefaultfalsebooleanRenders your single child (an <a>, a router Link, a <label> around a radio) as the card, with its look and press. A link card cannot hold buttons; use a CardLink title for that. The demo swaps in a link and drops the like button.
childrenReactNodeWith the simple props, the body (inside CardBody). Without them, the parts: CardMedia, CardHeader, CardBody, CardFooter.
actionReactNodeSimple API: a control at the end of the header (a menu, a like, a badge). It stays clickable above a CardLink.
mediaReactNodeSimple API: an image URL, a <CardMedia> (for aspect, overlays, inset), or any node (a chart, a map), which fills the media slot at its own height.
footerReactNodeSimple API: footer content (end-aligned), or a <CardFooter> for its own align and divided.
onClick(event: MouseEvent<HTMLDivElement>) => voidMakes the card a button: role=button, a Tab stop, Enter and Space, interactive on. Not called while disabled. Prefer asChild with a real <a> for navigation.
classNamesPartial<Record<"media" | "content" | "header" | "title" | "description" | "action" | "body" | "footer", string>>Extra classes for the parts the simple API renders.
classNamestringExtra classes for the card itself, merged last. Override the spacing vars here, e.g. [--card-padding:12px].
Parts
CardMedia
componentPhoto or custom media that bleeds to the card edges (it reads --card-padding). Its children sit on top of the photo (a badge, a like button; direct child controls stay clickable above a CardLink), or are the media when there is no src.
srcstringImage URL, drawn with object-cover.
altdefault""stringAlt text; empty marks the photo decorative. Describe it only when it says something the text does not.
aspectdefault"16/10""video" | "square" | "4/3" | "16/10" | "auto"The box ratio; auto takes the content's own height.
position"top" | "start"Where the media sits; inferred from the card's orientation.
insetdefaultfalsebooleanKeeps the card padding around the media and rounds its corners instead of bleeding to the edges.
zoomdefaulttruebooleanScales the photo up a touch on hover in interactive cards.
loadingdefault"lazy""lazy" | "eager"The image loading strategy.
childrenReactNodeOverlays on the photo, or the media itself (a chart, a map) when there is no src.
classNamesPartial<Record<"image", string>>Extra classes for the img.
CardHeader
componentGrid with CardTitle and CardDescription on the start and CardAction at the end, centered on a lone title and top-aligned on two lines. Takes div props.
CardTitle
componentAn h3 (15px semibold at md, 14 / 17px at sm / lg) that takes the tone color on tinted cards. Takes h3 props.
asChilddefaultfalsebooleanRenders your child (another heading level) with the title styles.
CardDescription
componentThe muted line under the title; deepens on tinted cards so it keeps 4.5:1. Takes p props.
CardAction
componentThe header's end column for a control; relative z-[2], so it stays clickable above a CardLink. Takes div props.
CardBody
componentBody text at the card's size (13 / 14 / 15px). Takes div props.
CardContent
componentThe column next to start media in a horizontal card (the simple API always renders it). Takes div props.
CardLink
componentA stretched link for the title: its ::after covers the card, so a click anywhere opens it (new tab, middle-click and long-press menus included) while CardAction and the direct controls of CardMedia and CardFooter stay separate (give other controls relative z-[2]). As the title prop it is described by the description prop, so a screen reader reads both. The card shows the focus ring; in a disabled card the link leaves the tab order. Takes a props.
asChilddefaultfalsebooleanRenders a router Link (or any anchor component) as the stretched link.
Examples
Variants
elevated on a page, outline for quiet frames, soft as the flat gray tile, ghost for padding only.
Stat cards
onClick makes a card a keyboard button (Enter, Space); selected rings the metric in focus.
Settings with a divided footer
The footer hairline runs edge to edge; Save stays disabled until something changes.
Horizontal link card
Media at the start from 640px up, on top on phones. The title is a CardLink; the phone button stays its own button.
Link cards
A CardLink title stretches over the card: one Tab stop, the ring around the whole card, native new-tab.
Selectable plans
A native radio group: arrow keys move the choice, the selected card takes the brand ring.
Tinted
soft and outline cards take a tone or any color; the title follows and the text keeps its contrast.
Sizes
Padding 16, 20 and 24px (one step less on phones) with the title scaling along.
Dependencies
Files
- components/ui/card.tsx
FAQ
Is the React Card component free?
Yes. The source is free and needs no account: copy it from the Code tab or install it with the CLI.
How do I install Card?
Run npx wingo-ui@latest add card. It also adds the Wingo UI items it builds on (Tones and UIProvider) and the npm package @radix-ui/react-slot. With shadcn, run npx shadcn@latest add https://wingo-ui.com/r/card.json.
See also
Used in
More React layout components
Articles using this component
- Codex Frontend Design Skill: Pair It With Real Components
Building UI with AI agents10 min read
- shadcn Tailwind v4 Migration: Step by Step From v3
Next.js and Tailwind CSS10 min read
- Tailwind v4 Nextjs 16 Setup: Config, Fonts and 5 Fixes
Next.js and Tailwind CSS9 min read
- shadcn Vite Setup with Tailwind CSS v4 and React 19
Next.js and Tailwind CSS7 min read
