Skip to content
Drivn
5 min read

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.

1import { Card } from "@/components/ui/card"
2
3export 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.

1import { Card } from "@/components/ui/card"
2
3const items = [
4 { id: 1, icon: "★", name: "Brush" },
5 { id: 2, icon: "▲", name: "Ruler" },
6 { id: 3, icon: "✨", name: "Sparkle" },
7]
8
9export 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>

Custom info row with action button

The Card.Info slot uses flex justify-between items-center by default, which means content placed at the start aligns left and content at the end aligns right. Drop a title on the left and a small Button on the right and you get an action card without any extra layout work.

The pattern works well for plugin install rows, settings toggles, and quick-action lists. Pair the Card with the Button component in secondary or outline variant for the right visual weight — a primary button inside a card competes with the card itself for attention. Set hover={false} on the Card so the lift does not fire when the user is aiming at the inner button.

1import { Card } from "@/components/ui/card"
2import { Button } from "@/components/ui/button"
3
4export default function ActionCard() {
5 return (
6 <Card hover={false}>
7 <Card.Preview>
8 <span className="text-2xl"></span>
9 </Card.Preview>
10 <Card.Info>
11 <p className="text-sm font-semibold">Plugin name</p>
12 <Button variant="outline" size="sm">
13 Install
14 </Button>
15 </Card.Info>
16 </Card>
17 )
18}
Get started

Install Drivn in one command

Copy the source into your project and own every line. Zero runtime dependencies, pure React + Tailwind.

npx drivn@latest create

Requires Node 18+. Works with npm, pnpm, and yarn.

Enjoying Drivn?
Star the repo on GitHub to follow new component releases.
Star →

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.