What Is a Tailwind Card?
In practice it’s a content container made from utility classes, holding an image, some text and an action inside one padded, rounded box.
There is nothing to import. The v1 documentation states that the framework ships no pre-designed card components, and the v4.3 utility reference lists none either (Tailwind Labs).
Because Tailwind is a utility-first framework, spacing, radius, shadow and border all live in the markup. You compose a card rather than enable one.
daisyUI goes the other way and adds a ready-made card class through a plugin. The hand-built version has one real advantage, which is that every value sits right there in the HTML where you can edit it.
Most cards break down into the same few parts.
- The outer article or div carries the background, border, radius and shadow.
- An img or a background-image div goes first so it runs edge to edge.
- The title (an h2 or h3) and the description sit inside a padded wrapper.
- A link, button or metadata line closes out the card at the bottom.
Markup order is also reading order and tab order, so the title link should come before any secondary action.
This structure also answers a wider card layout question, which is how to chunk related content so a grid of items stays scannable.
Preflight, Tailwind’s base reset, decides what you start with. Margins are gone, headings have no styling, images get display: block and max-width: 100%, and the border class draws a solid 1px line in currentColor (Tailwind Labs, Preflight docs).
How to Build a Basic Tailwind Card
Building one is mostly a matter of choosing the container, adding the surface classes, and then working inward toward the text and the action.
- Use an
article for the container when the card is a self-contained item such as a post or product, and a div otherwise. Cap the width with max-w-sm, which is 24rem. - Add the surface with
bg-white, rounded-xl, border border-gray-200 and shadow-sm. Name the border color, because v4 draws borders in currentColor by default. - Keep the image outside the padded wrapper so it runs edge to edge, and put
overflow-hidden on the container so the image follows the rounded corners. - Pad the body with
p-6 and style the text. Preflight leaves headings unstyled, so the title needs its own size and weight, for example text-lg font-semibold. - Add the action as a link or button. Hover and focus styles for it come further down.
- Test in Tailwind Play, the online editor at play.tailwindcss.com, or in a local file with the Play CDN script. The Play CDN is designed for development only, not for production (Tailwind CSS docs).
Steps 1 to 5 assume Tailwind is already installed. If it isn’t, set it up in the project first.
Put together, the markup looks like this.
<article class="max-w-sm overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm">
<img class="aspect-video w-full object-cover" src="/img/lake.jpg" alt="Mountain lake at sunrise">
<div class="p-6">
<h3 class="text-lg font-semibold text-gray-900">Card title</h3>
<p class="mt-2 text-sm text-gray-600">A short description that supports the title.</p>
<a href="/post" class="mt-4 inline-block text-sm font-medium text-indigo-600 hover:text-indigo-500">Read more</a>
</div>
</article>
Shadow depth is the value most people end up tuning by eye. Try raw values in a CSS box shadow generator and you’ll see which step (xs, sm, md or lg) fits. I tend to start at the light end and only go heavier if the card gets lost against the page.
Snippets copied from v3 tutorials render differently in v4 because the class names in the shadow and radius scales each shifted down one step (Tailwind upgrade guide).
| Look | v3 class | v4 class |
|---|
| Subtle shadow | shadow-sm | shadow-xs |
| Default shadow | shadow | shadow-sm |
| Small radius | rounded-sm | rounded-xs |
| Default radius | rounded | rounded-sm |
The old names still exist in v4, but each one now points to the next size up. A v3 shadow-sm card therefore looks heavier in v4 until it is renamed to shadow-xs.
Vertical, Horizontal and Image Card Layouts
Stack the image above the text for a vertical card, and add flex at a breakpoint for a horizontal one. The parts stay the same either way. Only the container direction and the image sizing change.
Vertical card with a top image
The default layout needs no flex at all. The image comes first, the padded body second, and normal block flow does the rest.
Give the image w-full plus a fixed height or an aspect class. Put overflow-hidden rounded-xl on the container to clip the corners, and p-6 on a wrapper div around the body.
Preflight sets images to display: block, so no stray gap opens under the image (Tailwind Labs).
Horizontal card with a side image
Horizontal cards stack on mobile and sit side by side from the md breakpoint, which starts at 48rem (Tailwind CSS docs).
<article class="flex flex-col overflow-hidden rounded-xl border border-gray-200 bg-white md:flex-row">
<img class="h-48 w-full object-cover md:h-auto md:w-48" src="/img/lake.jpg" alt="Mountain lake at sunrise">
<div class="p-6">
<h3 class="text-lg font-semibold text-gray-900">Card title</h3>
<p class="mt-2 text-sm text-gray-600">Text sits beside the image from 48rem up.</p>
</div>
</article>
The catch is that the image needs a fixed width on large screens and an explicit height on small ones, or object-cover has no box to crop against.
Fitting and clipping the image
Card images mostly come down to the object-fit utilities (Tailwind CSS docs).
| Class | CSS | Result on a card |
|---|
| object-cover | object-fit: cover | Fills the box, crops the edges |
| object-contain | object-fit: contain | Shows the whole image, leaves empty bars |
| object-fill | object-fit: fill | Stretches the image and distorts it |
Fix the box first, then fill it. Setting the image’s aspect ratio with aspect-video gives a 16 / 9 box, and object-cover crops the photo to fit without distortion.
Be aware that overflow-hidden also cuts off anything a child draws outside the card, focus rings and child shadows included. The card’s own shadow isn’t clipped.
Hover, Focus and Transition Effects
For a lift, pair hover:shadow-md with transition-shadow. Put group on the card if children should react through group-hover:, and give every hover style a focus-visible: counterpart.
shadow-sm hover:shadow-md transition-shadow deepens the shadow without moving the layout.- For a zoom, use
group-hover:scale-105 on the img, with overflow-hidden on its wrapper to crop the growth. - Adding
group-hover:text-indigo-600 to the title changes its color whenever the card is hovered anywhere.
The Tailwind docs demonstrate the group pattern with a project card. Hovering it changes the icon stroke and both text elements together (Tailwind CSS docs).
With hand-written CSS card hover effects, every state gets bundled into one class, whereas Tailwind hangs each state on the element as a separate utility.
In v4 the hover variant applies only when the primary input device supports hover, through @media (hover: hover) (Tailwind upgrade guide). A tap on a phone will not trigger the lift, and the docs advise treating hover as an enhancement. Anything a user needs, such as a revealed button, requires a visible default state.
The focus-visible variant matches keyboard focus. The bare ring utility also changed in v4: it is now 1px in currentColor, down from 3px in blue-500, so write ring-3 ring-blue-500 to get the v3 look (Tailwind upgrade guide).
Wrap movement in motion-safe:, for example motion-safe:group-hover:scale-105, so users who request reduced motion get no zoom.
There’s one v4 trap here. If you write a custom transition list, name scale instead of transform, as in transition-[opacity,scale], or the scale change snaps instead of animating.
How to Make an Entire Card Clickable and Accessible
Put one link on the card title and stretch its hit area across the card with a pseudo-element. Wrapping the whole card in an anchor places headings, paragraphs and buttons inside a single link, and nested links are invalid HTML.
<article class="relative overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm focus-within:outline-2 focus-within:outline-offset-2 focus-within:outline-indigo-600">
<img class="aspect-video w-full object-cover" src="/img/lake.jpg" alt="">
<div class="p-6">
<h3 class="text-lg font-semibold text-gray-900">
<a href="/post" class="after:absolute after:inset-0 focus:outline-hidden">Card title</a>
</h3>
<p class="mt-2 text-sm text-gray-600">A short description.</p>
</div>
</article>
The after: variant adds empty content automatically, so after:absolute after:inset-0 is enough once the card has relative (Tailwind CSS docs). The empty alt on the image keeps the announcement to the title alone.
That gives each card one tab stop and one link announcement, and mouse and touch users hit the whole card. The cost is that text under the overlay can’t be selected with the mouse.
WCAG 2.2 keeps 2.4.7 Focus Visible at Level AA. Any keyboard operable interface needs a mode where the focus indicator is visible (W3C).
That is why the snippet draws the indicator on the card with focus-within, so it surrounds the whole clickable area. Focus indicators count as non-text content and fall under 1.4.11 Non-text Contrast, so check the outline’s color contrast against both the card and the page background.
Buttons or links inside the card need relative z-10 to sit above the overlay, otherwise the stretched link swallows their clicks.
Pick h2, h3 or h4 to match the page outline. Preflight leaves headings unstyled, so changing the level has no visual side effect.
For a grid of cards, use a ul with role="list". Preflight removes list styling, and VoiceOver stops announcing unstyled lists as lists without that role (Tailwind Labs, Preflight docs).
Native HTML covers this pattern, so skip ARIA labels on the link and let the visible title name it. ARIA belongs where native elements fall short.
Card Grids, Breakpoints and Container Queries
Start with one column, step up with md:grid-cols-2 and lg:grid-cols-3, and let gap-6 handle the spacing.
<ul role="list" class="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3">
<li><!-- card --></li>
<li><!-- card --></li>
<li><!-- card --></li>
</ul>
Tailwind is built around mobile-first design. Unprefixed utilities apply at every width, and prefixed ones apply at that breakpoint and above.
That is why sm: means “from the small breakpoint up”, never “on phones” (Tailwind CSS docs).
Grid cells stretch by default, so cards in one row share a height. The contents do not follow unless the card is a flex column.
Put h-full on each card so it fills its grid cell, and flex flex-col on the card so the body can stretch. Then mt-auto on the footer or action row pins it to the bottom.
For a grid that picks its own column count, use grid-cols-[repeat(auto-fill,minmax(16rem,1fr))]. It needs no breakpoints. Swap in auto-fit only if a single short row should stretch its cards across the full width, because auto-fit collapses the empty tracks that auto-fill keeps.
Container queries are part of Tailwind core since v4.0, released January 22, 2025, so the separate container-queries plugin is gone (Tailwind Labs).
Viewport-based media queries read the screen. A container query reads the parent, so one card can sit horizontal in a wide main column and stacked in a narrow sidebar.
<div class="@container">
<article class="flex flex-col gap-4 @md:flex-row">
<!-- image and body -->
</article>
</div>
Default container sizes run from 16rem (@3xs) to 80rem (@7xl), and @md starts at 28rem (Tailwind CSS docs). The element marked @container cannot restyle itself. Only its descendants respond.
Dark Mode Styling for Cards
Every light color on the card needs a dark: partner, from the surface and text to the border. The default variant follows the operating system through prefers-color-scheme.
A typical set of pairs looks like this.
bg-white becomes dark:bg-gray-900 on the surface.- The title swaps
text-gray-900 for dark:text-white. - Description text goes from
text-gray-600 to dark:text-gray-400. border-gray-200 turns into dark:border-gray-800.
Shadows nearly vanish on dark surfaces. The card in the Tailwind docs drops its shadow in dark mode and uses a faint white outline instead, through dark:shadow-none and dark:outline-white/10 (Tailwind CSS docs).
To switch themes with a button instead of the system setting, override the variant in your CSS.
@custom-variant dark (&:where(.dark, .dark *));
Now dark: utilities apply whenever a dark class sits higher in the tree, usually on the html element. A data attribute works the same way with [data-theme=dark] in place of the class.
For a light, dark and system choice, read localStorage and matchMedia in an inline script in the head. That stops the page from flashing the wrong theme on load (Tailwind CSS docs).
WCAG 1.4.3 requires a 4.5:1 contrast ratio for normal text and 3:1 for large text, meaning 18 point or 14 point bold (W3C).
Muted description text is the pair most likely to slip below that line, so test it instead of trusting the shade names. An accessible color palette generator checks light and dark pairs before they reach the markup.
Build Your Own Cards or Use a Component Library?
Build your own when the card is a signature element of the design, and use a library when the card has to match a large set of other components. For a single card, a library rarely earns its install.
Prebuilt card options compared
| Option | Card approach | Cost or license | Setup |
|---|
| Tailwind Plus | UI Blocks as HTML, React or Vue markup | One-time paid license, closed to new customers | Copy markup |
| Flowbite | Static card markup, optional JS for interactive parts | MIT | Plugin and JS file |
| daisyUI | Classes: card, card-body, card-title, card-side | Open source | Plugin |
| shadcn/ui | React Card with CardHeader, CardContent, CardFooter | Open source | CLI adds the component |
daisyUI covers sizes from card-xs to card-xl and a card-side modifier for horizontal layouts. shadcn/ui installs through pnpm dlx shadcn@latest add card, lands at @/components/ui/card in your project, and takes a size="sm" prop plus a --card-spacing variable for density.
HyperUI is pure copy and paste. It has no installation and an MIT license (HyperUI README). Its component library at hyperui.dev lists card, blog card, product card and pricing sections (HyperUI). Preline UI offers free blocks under an MIT license and a Fair Use License, with a paid Pro tier above them (Preline README).
Tailwind Labs closed Tailwind Plus to new customers on September 9, 2026, the day it announced it is joining Shopify. Existing customers keep access to what they bought. The one-time price the official checkout listed before the closure was $299 for a personal license (Tailwind Labs), so it no longer applies to a new purchase. For a new project, pick one of the other options in the table.
Going with a library means matching buttons, modals and dropdowns come in the same set, and sizes, borders and horizontal layouts already exist as classes or props. You also take on an extra dependency, plus JavaScript for the interactive parts (Flowbite, Preline).
Going custom means no dependency, every value stays editable in the markup, and the card follows your own design tokens. Accessibility and dark mode testing are yours to do, though, and variants multiply unless the card is wrapped in a component.
Building a reusable card yourself
The Tailwind docs name components as the best strategy for reuse in React, Svelte and Vue, and template partials for Blade, ERB, Twig or Nunjucks (Tailwind CSS docs). If the project is React, the walkthrough on using Tailwind CSS in React covers setup before the component below.
export function Card({ title, href, tone = "plain", children }) {
const tones = {
plain: "border-gray-200 bg-white text-gray-900",
inverted: "border-gray-800 bg-gray-900 text-white",
};
return (
<article className={`relative rounded-xl border p-6 shadow-sm ${tones[tone]}`}>
<h3 className="text-lg font-semibold">
<a href={href} className="after:absolute after:inset-0">{title}</a>
</h3>
<div className="mt-2 text-sm">{children}</div>
</article>
);
}
Vue follows the same pattern in a single-file component. If its style block uses @apply, v4 needs an @reference to the main stylesheet, because separately bundled styles cannot see theme variables (Tailwind upgrade guide).
The docs’ own custom CSS example for a button class uses theme variables instead of @apply. Utilities in the template are simpler still.
IntelliSense in VS Code adds autocomplete, linting and hover previews for class names. The Prettier plugin sorts every class list into Tailwind’s recommended order, so copied cards produce clean diffs.
Where Tailwind Cards Break
Cards go wrong in fairly predictable places. String-built class names, snippets from older versions, browsers below the v4 floor, text that runs long, and extra classes passed in from outside cause most of the trouble.
| Failure | What you see | Fix |
|---|
| Dynamic class names | A variant renders unstyled | Look up complete class names in a map, or safelist with @source inline() |
| Stale snippets | Odd spacing, shadows or rings | Check the loaded version, then run npx @tailwindcss/upgrade (Node.js 20+) |
| Old browsers | v4 utilities have no effect | Stay on v3.4 or raise the support floor |
| Long text | Titles wrap to many lines and cards grow unevenly | line-clamp-2 on the title, line-clamp-3 on the description |
| Classes passed from outside | An override does nothing | Expose props instead of accepting extra classes |
Tailwind scans source files as plain text and cannot read string interpolation. A class like bg-${color}-600 never exists as a full string, so the CSS is never generated (Tailwind CSS docs).
Conflicts between classes resolve by stylesheet order, not attribute order. When two utilities target the same property, the later rule in the generated CSS wins.
Stale code is easy to find. Many card tutorials still load the prebuilt v1 or v2 stylesheets from a CDN, versions that predate v4 by years.
The two Play CDN scripts differ too. The v3 docs load cdn.tailwindcss.com, while v4 uses @tailwindcss/browser@4 (Tailwind CSS docs).
Tailwind CSS v4.0 targets Safari 16.4+, Chrome 111+ and Firefox 128+, and the upgrade guide tells projects that need older browsers to stay on v3.4 (Tailwind Labs). Check your cross-browser compatibility requirements before pasting a v4 card into a legacy site.
Tailwind Card FAQ
How do pricing, profile, blog and product cards differ?
Each type keeps the same container and swaps the content blocks. Blog cards lead with an image, date and excerpt, and product cards add a price and a cart button.
Pricing cards drop the image for a plan name, price and feature list. Profile cards center a rounded-full avatar above a name and role.
How does a Tailwind card compare with a Bootstrap card?
Bootstrap ships a card class with built-in parts, while Tailwind provides none and builds the card from utilities in the markup.
That makes a Bootstrap card quicker to start and a Tailwind card easier to restyle in place, because no component stylesheet needs overriding.
Can a card have a gradient border?
Yes. Wrap the card in a div with bg-linear-to-r from-indigo-500 to-pink-500 p-px rounded-xl, then give the inner card a solid background and a slightly smaller radius, such as rounded-[11px].
The 1px of padding shows the gradient as a border. Tailwind v4 renamed the older bg-gradient-to-r class to bg-linear-to-r (Tailwind Labs, 2025).
How do I build a flip card with Tailwind?
Put perspective-distant on the wrapper, transform-3d and transition-transform on the inner box, and group-hover:rotate-y-180 to turn it. Both faces need backface-hidden, and the back face starts at rotate-y-180.
In v4, hover applies only on hover-capable devices, so add a focus or tap toggle for phones. A plain CSS flip card follows the same structure.
How do I truncate long text in a card?
Use truncate for a single line and line-clamp-3 for three lines. Both end the cut text with an ellipsis.
Inside a flex row, add min-w-0 to the text container. Without it, the text keeps its full width and never truncates.
Building a Tailwind Card in the Right Order
Decisions hold up best in a fixed sequence, with markup first, then layout, and decoration at the end.
Semantic elements and heading level change the HTML structure, so fixing them late means rewriting the card. Shadow, radius and color classes swap in seconds at any stage.
- Semantic element, heading level and link pattern
- Flex or grid layout, with a breakpoint or container query
- Hover, focus and dark variants last
The trade-off is a plain first render. The card looks unfinished until the third step, so judge it by keyboard behavior before appearance.
This order was checked against the Tailwind v4.3 documentation on October 29, 2026. A future major release that renames utilities, as v4 did with shadow-sm and rounded-sm, would change class names but leave the sequence intact.