React Card Component Examples
Drop-in React Card examples — showcase grid, hover toggle, icon previews, link wrappers, action rows. Zero runtime UI deps. Plain React under Tailwind.
A card is the smallest unit of "one thing you can click." Component galleries, plugin marketplaces, template pickers, product grids — they all reduce to the same tile: a visual region on top, a compact label underneath, and a hover state saying the whole surface is a target. Drivn's Card is built narrowly for that shape rather than as a general-purpose content box. The root is w-48 aspect-square flex flex-col, Card.Preview takes flex-1 and centers whatever you hand it, and Card.Info closes the tile with p-5 flex justify-between items-center.
The examples here are the arrangements that keep recurring in shipped products: the two-slot default, a uniform grid, the static variant with the lift switched off, a tile that routes, and an info row carrying an inline action. Each imports from @/components/ui/card — the path npx drivn add card installs to — so a snippet compiles the moment you paste it into a page.
When your layout runs taller than it is wide, or wants a footer band under the body, override className and let cn merge your utilities over the defaults: w-full aspect-auto releases both size constraints at once. The slots underneath are plain divs, so any flex or grid you nest inside them behaves normally. Start from the closest example and change one thing.
Compound layout with Preview and Info
The default Card composes two slots: Card.Preview fills the top region with flex-1 and centers its child via flex items-center justify-center, and Card.Info sits below with p-5 padding and a horizontal flex split. Drop an icon, image, or illustration into the preview slot, and a title plus description into the info slot.
Because the Card source lives in your repo after install, swapping the rounded corner radius, the border color, or the info padding is a local edit rather than a theme override. The snippet below is the minimum viable Card with both slots: a single emoji as the preview and a two-line label as the info. No wrapper div, no extra state.
1 import { Card } from "@/components/ui/card" 2 3 export default function Page() { 4 return ( 5 <Card> 6 <Card.Preview> 7 <span className="text-2xl">★</span> 8 </Card.Preview> 9 <Card.Info> 10 <div> 11 <p className="text-sm font-semibold text-foreground"> 12 Card title 13 </p> 14 <p className="text-xs text-muted-foreground"> 15 Description text 16 </p> 17 </div> 18 </Card.Info> 19 </Card> 20 ) 21 }
Showcase grid
Because each Card carries its own dimensions, a gallery asks almost nothing of the parent. Wrap the tiles in grid gap-4 with a column count and that is the whole layout — no per-item sizing utilities, no aspect ratio on the wrapper, no min-height guesswork. Every tile lands at 192px square, borders line up across rows, and gap is the only spacing knob you touch.
Responsive layouts work the same way: grid-cols-2 md:grid-cols-3 lg:grid-cols-4 changes the column count per breakpoint while the Card stays fixed. That is the trade the w-48 aspect-square default makes — you give up fluid width and get alignment for free. If you want tiles that stretch to fill their track instead, pass className="w-full aspect-auto" and let the grid drive both axes, since cn merges the override ahead of the base classes. Drivn runs the fixed variant on the components index and the home page.
1 import { Card } from "@/components/ui/card" 2 3 const items = [ 4 { id: 1, icon: "★", name: "Brush" }, 5 { id: 2, icon: "▲", name: "Ruler" }, 6 { id: 3, icon: "✨", name: "Sparkle" }, 7 ] 8 9 export default function Gallery() { 10 return ( 11 <div className="grid grid-cols-3 gap-4"> 12 {items.map((item) => ( 13 <Card key={item.id}> 14 <Card.Preview> 15 <span className="text-2xl">{item.icon}</span> 16 </Card.Preview> 17 <Card.Info> 18 <p className="text-sm font-semibold"> 19 {item.name} 20 </p> 21 </Card.Info> 22 </Card> 23 ))} 24 </div> 25 ) 26 }
Toggling the hover lift
The hover prop defaults to true. When set, the base classes apply hover:bg-accent hover:border-border hover:-translate-y-1 — a 4px upward lift plus a background and border swap. To opt out for static cards (read-only summaries, non-interactive panels), pass hover={false} and the same Card renders without any hover transition.
This matters when a card is wrapped in a non-interactive context — say a dashboard widget that displays a metric without being clickable. A hover lift on a non-interactive element implies affordance the user cannot act on, which is a small but real accessibility annoyance. Setting hover={false} is the right escape hatch. For cards that should look interactive but route to a different action than a click, see the link-wrapper example below.
1 <Card hover={false}> 2 <Card.Preview> 3 <span className="text-2xl">▦</span> 4 </Card.Preview> 5 <Card.Info> 6 <p className="text-sm font-semibold">Static metric</p> 7 </Card.Info> 8 </Card>
Card as a Next.js Link
To make a Card navigate when clicked, wrap it in a Next.js Link. The hover lift already in the Card signals interactivity, and the Link inherits the click target from the entire tile. Pass className="block" (or inline-block) to the Link if its default inline display crops the lift animation.
Keep accessibility in mind: a Link wrapping a Card should have meaningful inner text — a heading inside Card.Info does the job, since screen readers read the heading as the link label. Avoid wrapping a Link around a Card that already contains a Button, because nested interactive elements break keyboard tab order. If your card has both a primary destination and a secondary button action, use a card-level Link plus an onClick handler that calls event.preventDefault() on the inner button.
1 import Link from "next/link" 2 import { Card } from "@/components/ui/card" 3 4 export default function ComponentTile() { 5 return ( 6 <Link href="/docs/components/button" className="block"> 7 <Card> 8 <Card.Preview> 9 <span className="text-2xl">●</span> 10 </Card.Preview> 11 <Card.Info> 12 <p className="text-sm font-semibold">Button</p> 13 </Card.Info> 14 </Card> 15 </Link> 16 ) 17 }
Install Drivn in one command
Copy the source into your project and own every line. Zero runtime dependencies, pure React + Tailwind.
npx drivn@latest createRequires Node 18+. Works with npm, pnpm, and yarn.
Frequently asked questions
No. The Card source imports React from react and a cn class merger from your utils — that is the full runtime. There is no Radix slot, no cva, no clsx wrapper around the children. The component renders to three plain divs (root, preview, info) styled with Tailwind, and the dot notation API is built with Object.assign instead of a context provider.
Pass a className that overrides the width and aspect ratio: <Card className="w-full aspect-auto">…</Card>. The cn utility merges the override with the base classes, so w-full wins over w-48 and aspect-auto releases the square ratio. The flex split, border, padding, and hover behavior stay intact unless you also override them.
Yes. The Card root renders children directly, so you can add a third div between Card.Preview and Card.Info, or compose multiple Card.Info rows. The default vertical flex layout stacks every direct child, so any number of regions render top-to-bottom. If you go beyond three regions, consider whether shadcn/ui's Card with its dedicated header / content / footer slots fits the editorial layout better.
Wrap the Card in a Next.js Link with className="block". The Link makes the entire Card clickable and inherits routing prefetch behavior. Avoid placing buttons or other interactive elements inside a Linked Card — nested interactives break keyboard tab order. If you need a card with both navigation and a button action, render the Link as the title inside Card.Info instead of wrapping the whole tile.
The 20px radius lands between Tailwind's rounded-2xl (16px) and rounded-3xl (24px) — close enough that neither preset fits exactly. The arbitrary value pins the shape Drivn ships across every example. To change it, edit the base entry of the styles object in your local copy of the Card source after installing — the Drivn philosophy is "copy and own", so the file is yours to modify.

