02 / 14
All 14 in 14 Material Design Components in Tailwind CSS
- 01Tailwind Material Design 3 Buttons
- 02Tailwind Material Design 3 Cards
- 03Tailwind Material Design Navigation Bar, Rail and Drawer
- 04Tailwind Material Design Text Fields and Form Controls
- 05Tailwind Material Design Dialog, Bottom Sheet and Snackbar
- 06Tailwind Material Design Data Table
- 07Tailwind Material Design Tabs with Sliding Indicator
- 08Tailwind Material Design Chips with Live Filtering
- 09Tailwind Material Design Stepper and Checkout Flow
- 10Tailwind Material Design Tooltips, Progress and Badges
- 11Tailwind Material Design Expansion Panels and Accordion
- 12Tailwind Material Design Lists and Swipeable Rows
- 13Tailwind Material Design Color Palette and Theme Switcher
- 14Tailwind Material Design Responsive App Shell Layout
Tailwind Material Design 3 Cards
The three M3 card containers — elevated, filled and outlined — plus a horizontal media card and a stat tile, laid out on a grid that reflows with no media query. The port's real gain is group-hover:: the whole card is the hover target, so the title colour, the elevation and the media zoom all respond from one hovered ancestor instead of three descendant rules.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section class="tmd-02 w-full min-h-svh bg-tmd02-bg font-tmd02-sans text-tmd02-onsurface px-5 py-9" aria-label="Material Design 3 cards demo">
<div class="w-[min(1100px,100%)] mx-auto grid gap-6">
<header class="grid gap-1.5">
<h2 class="text-[26px] font-medium tracking-[-0.01em]">Material Design 3 cards</h2>
<p class="text-sm text-tmd02-onvariant">Elevated, filled and outlined containers — one <code class="font-tmd02-mono text-[12px]">group</code> per card drives every hover response.</p>
</header>
<div class="grid gap-4 grid-cols-[repeat(auto-fit,minmax(min(260px,100%),1fr))]">
<article class="group grid rounded-xl bg-tmd02-surface shadow-tmd02-1 transition-shadow duration-250 hover:shadow-tmd02-3 focus-within:shadow-tmd02-3 motion-reduce:transition-none">
<div class="overflow-hidden rounded-t-xl">
<div class="aspect-[16/10] bg-linear-[145deg,#1565c0,#42a5f5_70%,#90caf9] transition-transform duration-400 group-hover:scale-105 motion-reduce:transition-none" role="img" aria-label="Coastal route illustration"></div>
</div>
<div class="grid gap-1.5 p-4">
<span class="font-tmd02-mono text-[11px] uppercase tracking-[0.1em] text-tmd02-onvariant">Elevated</span>
<h3 class="text-lg font-medium tracking-[-0.005em] transition-colors duration-200 group-hover:text-tmd02-primary"><a href="#route" class="text-inherit no-underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tmd02-primary rounded-sm">Coastal route, revisited</a></h3>
<p class="text-sm leading-6 text-tmd02-onvariant text-pretty line-clamp-2">Fourteen kilometres of cliff path, mapped for the first time since the 1998 survey.</p>
</div>
<div class="flex gap-2 px-3 pb-3">
<button type="button" class="relative overflow-hidden min-h-10 px-4 rounded-full border-0 bg-transparent cursor-pointer text-sm font-medium text-tmd02-primary focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tmd02-primary before:absolute before:inset-0 before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08] active:before:opacity-[0.12]">Open</button>
<button type="button" class="relative overflow-hidden min-h-10 px-4 rounded-full border border-tmd02-outline bg-transparent cursor-pointer text-sm font-medium text-tmd02-primary focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tmd02-primary before:absolute before:inset-0 before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08] active:before:opacity-[0.12]">Share</button>
</div>
</article>
<article class="group grid rounded-xl bg-tmd02-surfacevariant">
<div class="overflow-hidden rounded-t-xl">
<div class="aspect-[16/10] bg-linear-[145deg,#00695c,#26a69a_70%,#b2dfdb] transition-transform duration-400 group-hover:scale-105 motion-reduce:transition-none" role="img" aria-label="Field station illustration"></div>
</div>
<div class="grid gap-1.5 p-4">
<span class="font-tmd02-mono text-[11px] uppercase tracking-[0.1em] text-tmd02-onvariant">Filled</span>
<h3 class="text-lg font-medium tracking-[-0.005em] transition-colors duration-200 group-hover:text-tmd02-primary">Station 4 telemetry</h3>
<p class="text-sm leading-6 text-tmd02-onvariant text-pretty line-clamp-2">Filled cards sit on surface-variant with no shadow — the flattest of the three containers.</p>
</div>
<div class="flex items-center gap-1 px-3 pb-3">
<button type="button" aria-label="Add to favourites" class="relative overflow-hidden grid place-items-center min-w-11 min-h-11 rounded-full border-0 bg-transparent cursor-pointer text-tmd02-onvariant focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tmd02-primary before:absolute before:inset-1 before:rounded-full before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08]"><span class="font-tmd02-icon text-[20px] leading-none" aria-hidden="true">favorite</span></button>
<button type="button" aria-label="Download" class="relative overflow-hidden grid place-items-center min-w-11 min-h-11 rounded-full border-0 bg-transparent cursor-pointer text-tmd02-onvariant focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tmd02-primary before:absolute before:inset-1 before:rounded-full before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08]"><span class="font-tmd02-icon text-[20px] leading-none" aria-hidden="true">download</span></button>
<span class="ml-auto self-center font-tmd02-mono text-[11px] text-tmd02-onvariant">12.4 MB</span>
</div>
</article>
<article class="group grid rounded-xl bg-tmd02-surface border border-tmd02-outlinevariant transition-colors duration-200 hover:border-tmd02-primary">
<div class="grid gap-1.5 p-4">
<span class="font-tmd02-mono text-[11px] uppercase tracking-[0.1em] text-tmd02-onvariant">Outlined</span>
<h3 class="text-lg font-medium tracking-[-0.005em]">No shadow, one hairline</h3>
<p class="text-sm leading-6 text-tmd02-onvariant text-pretty">Outlined is the accessible default on busy backgrounds: a 1px <span class="font-tmd02-mono text-[12px]">outline-variant</span> border reads at any elevation, and there is nothing to repaint on hover.</p>
</div>
<dl class="grid grid-cols-3 gap-2 px-4 pb-4 m-0">
<div class="grid gap-0.5"><dt class="font-tmd02-mono text-[10px] uppercase tracking-[0.08em] text-tmd02-onvariant">Sites</dt><dd class="m-0 text-xl font-medium tabular-nums">18</dd></div>
<div class="grid gap-0.5"><dt class="font-tmd02-mono text-[10px] uppercase tracking-[0.08em] text-tmd02-onvariant">Depth</dt><dd class="m-0 text-xl font-medium tabular-nums">240m</dd></div>
<div class="grid gap-0.5"><dt class="font-tmd02-mono text-[10px] uppercase tracking-[0.08em] text-tmd02-onvariant">Uptime</dt><dd class="m-0 text-xl font-medium tabular-nums">99.1%</dd></div>
</dl>
</article>
</div>
<article class="group flex flex-wrap items-stretch gap-0 rounded-xl overflow-hidden bg-tmd02-surface shadow-tmd02-1 transition-shadow duration-250 hover:shadow-tmd02-2 motion-reduce:transition-none">
<div class="w-[min(200px,40%)] min-h-40 bg-linear-[150deg,#7b1fa2,#ba68c8_65%,#f8bbd0] transition-transform duration-400 group-hover:scale-105 motion-reduce:transition-none" role="img" aria-label="Album artwork"></div>
<div class="flex-1 min-w-[min(260px,100%)] grid content-center gap-2 p-5">
<span class="font-tmd02-mono text-[11px] uppercase tracking-[0.1em] text-tmd02-onvariant">Horizontal · media leading</span>
<h3 class="text-[21px] font-medium tracking-[-0.01em]">Tidal — deluxe edition</h3>
<p class="text-sm leading-6 text-tmd02-onvariant text-pretty">A horizontal card is the same container with <span class="font-tmd02-mono text-[12px]">flex-wrap</span> and a percentage media rail. Below 460px the rail wraps above the text with no breakpoint involved.</p>
<div class="flex gap-2 mt-1">
<button type="button" class="relative overflow-hidden inline-flex items-center gap-2 min-h-10 px-5 rounded-full border-0 bg-tmd02-primary text-tmd02-onprimary cursor-pointer text-sm font-medium focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tmd02-primary before:absolute before:inset-0 before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08] active:before:opacity-[0.12]"><span class="font-tmd02-icon text-[18px] leading-none" aria-hidden="true">play_arrow</span>Play</button>
<button type="button" class="relative overflow-hidden min-h-10 px-4 rounded-full border-0 bg-transparent cursor-pointer text-sm font-medium text-tmd02-primary focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tmd02-primary before:absolute before:inset-0 before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08]">Add to library</button>
</div>
</div>
</article>
</div>
</section><section class="tmd-02 w-full min-h-svh bg-tmd02-bg font-tmd02-sans text-tmd02-onsurface px-5 py-9" aria-label="Material Design 3 cards demo">
<div class="w-[min(1100px,100%)] mx-auto grid gap-6">
<header class="grid gap-1.5">
<h2 class="text-[26px] font-medium tracking-[-0.01em]">Material Design 3 cards</h2>
<p class="text-sm text-tmd02-onvariant">Elevated, filled and outlined containers — one <code class="font-tmd02-mono text-[12px]">group</code> per card drives every hover response.</p>
</header>
<div class="grid gap-4 grid-cols-[repeat(auto-fit,minmax(min(260px,100%),1fr))]">
<article class="group grid rounded-xl bg-tmd02-surface shadow-tmd02-1 transition-shadow duration-250 hover:shadow-tmd02-3 focus-within:shadow-tmd02-3 motion-reduce:transition-none">
<div class="overflow-hidden rounded-t-xl">
<div class="aspect-[16/10] bg-linear-[145deg,#1565c0,#42a5f5_70%,#90caf9] transition-transform duration-400 group-hover:scale-105 motion-reduce:transition-none" role="img" aria-label="Coastal route illustration"></div>
</div>
<div class="grid gap-1.5 p-4">
<span class="font-tmd02-mono text-[11px] uppercase tracking-[0.1em] text-tmd02-onvariant">Elevated</span>
<h3 class="text-lg font-medium tracking-[-0.005em] transition-colors duration-200 group-hover:text-tmd02-primary"><a href="#route" class="text-inherit no-underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tmd02-primary rounded-sm">Coastal route, revisited</a></h3>
<p class="text-sm leading-6 text-tmd02-onvariant text-pretty line-clamp-2">Fourteen kilometres of cliff path, mapped for the first time since the 1998 survey.</p>
</div>
<div class="flex gap-2 px-3 pb-3">
<button type="button" class="relative overflow-hidden min-h-10 px-4 rounded-full border-0 bg-transparent cursor-pointer text-sm font-medium text-tmd02-primary focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tmd02-primary before:absolute before:inset-0 before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08] active:before:opacity-[0.12]">Open</button>
<button type="button" class="relative overflow-hidden min-h-10 px-4 rounded-full border border-tmd02-outline bg-transparent cursor-pointer text-sm font-medium text-tmd02-primary focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tmd02-primary before:absolute before:inset-0 before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08] active:before:opacity-[0.12]">Share</button>
</div>
</article>
<article class="group grid rounded-xl bg-tmd02-surfacevariant">
<div class="overflow-hidden rounded-t-xl">
<div class="aspect-[16/10] bg-linear-[145deg,#00695c,#26a69a_70%,#b2dfdb] transition-transform duration-400 group-hover:scale-105 motion-reduce:transition-none" role="img" aria-label="Field station illustration"></div>
</div>
<div class="grid gap-1.5 p-4">
<span class="font-tmd02-mono text-[11px] uppercase tracking-[0.1em] text-tmd02-onvariant">Filled</span>
<h3 class="text-lg font-medium tracking-[-0.005em] transition-colors duration-200 group-hover:text-tmd02-primary">Station 4 telemetry</h3>
<p class="text-sm leading-6 text-tmd02-onvariant text-pretty line-clamp-2">Filled cards sit on surface-variant with no shadow — the flattest of the three containers.</p>
</div>
<div class="flex items-center gap-1 px-3 pb-3">
<button type="button" aria-label="Add to favourites" class="relative overflow-hidden grid place-items-center min-w-11 min-h-11 rounded-full border-0 bg-transparent cursor-pointer text-tmd02-onvariant focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tmd02-primary before:absolute before:inset-1 before:rounded-full before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08]"><span class="font-tmd02-icon text-[20px] leading-none" aria-hidden="true">favorite</span></button>
<button type="button" aria-label="Download" class="relative overflow-hidden grid place-items-center min-w-11 min-h-11 rounded-full border-0 bg-transparent cursor-pointer text-tmd02-onvariant focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tmd02-primary before:absolute before:inset-1 before:rounded-full before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08]"><span class="font-tmd02-icon text-[20px] leading-none" aria-hidden="true">download</span></button>
<span class="ml-auto self-center font-tmd02-mono text-[11px] text-tmd02-onvariant">12.4 MB</span>
</div>
</article>
<article class="group grid rounded-xl bg-tmd02-surface border border-tmd02-outlinevariant transition-colors duration-200 hover:border-tmd02-primary">
<div class="grid gap-1.5 p-4">
<span class="font-tmd02-mono text-[11px] uppercase tracking-[0.1em] text-tmd02-onvariant">Outlined</span>
<h3 class="text-lg font-medium tracking-[-0.005em]">No shadow, one hairline</h3>
<p class="text-sm leading-6 text-tmd02-onvariant text-pretty">Outlined is the accessible default on busy backgrounds: a 1px <span class="font-tmd02-mono text-[12px]">outline-variant</span> border reads at any elevation, and there is nothing to repaint on hover.</p>
</div>
<dl class="grid grid-cols-3 gap-2 px-4 pb-4 m-0">
<div class="grid gap-0.5"><dt class="font-tmd02-mono text-[10px] uppercase tracking-[0.08em] text-tmd02-onvariant">Sites</dt><dd class="m-0 text-xl font-medium tabular-nums">18</dd></div>
<div class="grid gap-0.5"><dt class="font-tmd02-mono text-[10px] uppercase tracking-[0.08em] text-tmd02-onvariant">Depth</dt><dd class="m-0 text-xl font-medium tabular-nums">240m</dd></div>
<div class="grid gap-0.5"><dt class="font-tmd02-mono text-[10px] uppercase tracking-[0.08em] text-tmd02-onvariant">Uptime</dt><dd class="m-0 text-xl font-medium tabular-nums">99.1%</dd></div>
</dl>
</article>
</div>
<article class="group flex flex-wrap items-stretch gap-0 rounded-xl overflow-hidden bg-tmd02-surface shadow-tmd02-1 transition-shadow duration-250 hover:shadow-tmd02-2 motion-reduce:transition-none">
<div class="w-[min(200px,40%)] min-h-40 bg-linear-[150deg,#7b1fa2,#ba68c8_65%,#f8bbd0] transition-transform duration-400 group-hover:scale-105 motion-reduce:transition-none" role="img" aria-label="Album artwork"></div>
<div class="flex-1 min-w-[min(260px,100%)] grid content-center gap-2 p-5">
<span class="font-tmd02-mono text-[11px] uppercase tracking-[0.1em] text-tmd02-onvariant">Horizontal · media leading</span>
<h3 class="text-[21px] font-medium tracking-[-0.01em]">Tidal — deluxe edition</h3>
<p class="text-sm leading-6 text-tmd02-onvariant text-pretty">A horizontal card is the same container with <span class="font-tmd02-mono text-[12px]">flex-wrap</span> and a percentage media rail. Below 460px the rail wraps above the text with no breakpoint involved.</p>
<div class="flex gap-2 mt-1">
<button type="button" class="relative overflow-hidden inline-flex items-center gap-2 min-h-10 px-5 rounded-full border-0 bg-tmd02-primary text-tmd02-onprimary cursor-pointer text-sm font-medium focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tmd02-primary before:absolute before:inset-0 before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08] active:before:opacity-[0.12]"><span class="font-tmd02-icon text-[18px] leading-none" aria-hidden="true">play_arrow</span>Play</button>
<button type="button" class="relative overflow-hidden min-h-10 px-4 rounded-full border-0 bg-transparent cursor-pointer text-sm font-medium text-tmd02-primary focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tmd02-primary before:absolute before:inset-0 before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08]">Add to library</button>
</div>
</div>
</article>
</div>
</section>@import "tailwindcss";
@theme {
--color-tmd02-bg: #eceff1;
--color-tmd02-bg: oklch(0.95 0.004 236.5);
--color-tmd02-surface: #fffbfe;
--color-tmd02-surface: oklch(0.992 0.006 334);
--color-tmd02-surfacevariant: #e7e0ec;
--color-tmd02-surfacevariant: oklch(0.916 0.018 311.6);
--color-tmd02-primary: #1565c0;
--color-tmd02-primary: oklch(0.513 0.16 255.7);
--color-tmd02-onprimary: #ffffff;
--color-tmd02-onprimary: oklch(1 0 89.9);
--color-tmd02-onsurface: #1c1b1f;
--color-tmd02-onsurface: oklch(0.225 0.008 297.2);
--color-tmd02-onvariant: #49454f;
--color-tmd02-onvariant: oklch(0.398 0.017 303.7);
--color-tmd02-outline: #79747e;
--color-tmd02-outline: oklch(0.567 0.016 308.1);
--color-tmd02-outlinevariant: #cac4d0;
--color-tmd02-outlinevariant: oklch(0.829 0.018 308.2);
--font-tmd02-sans: 'Roboto Flex', 'Roboto', system-ui, sans-serif;
--font-tmd02-mono: 'Roboto Mono', ui-monospace, monospace;
--font-tmd02-icon: 'Material Symbols Outlined';
--shadow-tmd02-1: 0 1px 2px 0 rgb(0 0 0 / 0.3), 0 1px 3px 1px rgb(0 0 0 / 0.15);
--shadow-tmd02-2: 0 1px 2px 0 rgb(0 0 0 / 0.3), 0 2px 6px 2px rgb(0 0 0 / 0.15);
--shadow-tmd02-3: 0 4px 8px 3px rgb(0 0 0 / 0.15), 0 1px 3px 0 rgb(0 0 0 / 0.3);
}
.tmd-02 {
width: 100%;
min-height: 100svh;
}
.tmd-02 [class*="font-tmd02-icon"] {
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
font-feature-settings: 'liga';
}@import "tailwindcss";
@theme {
--color-tmd02-bg: #eceff1;
--color-tmd02-bg: oklch(0.95 0.004 236.5);
--color-tmd02-surface: #fffbfe;
--color-tmd02-surface: oklch(0.992 0.006 334);
--color-tmd02-surfacevariant: #e7e0ec;
--color-tmd02-surfacevariant: oklch(0.916 0.018 311.6);
--color-tmd02-primary: #1565c0;
--color-tmd02-primary: oklch(0.513 0.16 255.7);
--color-tmd02-onprimary: #ffffff;
--color-tmd02-onprimary: oklch(1 0 89.9);
--color-tmd02-onsurface: #1c1b1f;
--color-tmd02-onsurface: oklch(0.225 0.008 297.2);
--color-tmd02-onvariant: #49454f;
--color-tmd02-onvariant: oklch(0.398 0.017 303.7);
--color-tmd02-outline: #79747e;
--color-tmd02-outline: oklch(0.567 0.016 308.1);
--color-tmd02-outlinevariant: #cac4d0;
--color-tmd02-outlinevariant: oklch(0.829 0.018 308.2);
--font-tmd02-sans: 'Roboto Flex', 'Roboto', system-ui, sans-serif;
--font-tmd02-mono: 'Roboto Mono', ui-monospace, monospace;
--font-tmd02-icon: 'Material Symbols Outlined';
--shadow-tmd02-1: 0 1px 2px 0 rgb(0 0 0 / 0.3), 0 1px 3px 1px rgb(0 0 0 / 0.15);
--shadow-tmd02-2: 0 1px 2px 0 rgb(0 0 0 / 0.3), 0 2px 6px 2px rgb(0 0 0 / 0.15);
--shadow-tmd02-3: 0 4px 8px 3px rgb(0 0 0 / 0.15), 0 1px 3px 0 rgb(0 0 0 / 0.3);
}
.tmd-02 {
width: 100%;
min-height: 100svh;
}
.tmd-02 [class*="font-tmd02-icon"] {
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
font-feature-settings: 'liga';
}Setup — do this once per project
Skip this if your project already runs Tailwind v4.
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>Compiles in the browser — perfect for a CodePen or a quick test, not for production.
npm install tailwindcss @tailwindcss/vite
npm install tailwindcss @tailwindcss/vite// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})Any v4 setup works — Vite, PostCSS or the CLI. Nothing in this demo is bundler-specific.
@import "tailwindcss";
@theme {
--color-tmd02-bg: #eceff1;
--color-tmd02-bg: oklch(0.95 0.004 236.5);
--color-tmd02-surface: #fffbfe;
--color-tmd02-surface: oklch(0.992 0.006 334);
--color-tmd02-surfacevariant: #e7e0ec;
--color-tmd02-surfacevariant: oklch(0.916 0.018 311.6);
--color-tmd02-primary: #1565c0;
--color-tmd02-primary: oklch(0.513 0.16 255.7);
--color-tmd02-onprimary: #ffffff;
--color-tmd02-onprimary: oklch(1 0 89.9);
--color-tmd02-onsurface: #1c1b1f;
--color-tmd02-onsurface: oklch(0.225 0.008 297.2);
--color-tmd02-onvariant: #49454f;
--color-tmd02-onvariant: oklch(0.398 0.017 303.7);
--color-tmd02-outline: #79747e;
--color-tmd02-outline: oklch(0.567 0.016 308.1);
--color-tmd02-outlinevariant: #cac4d0;
--color-tmd02-outlinevariant: oklch(0.829 0.018 308.2);
--font-tmd02-sans: 'Roboto Flex', 'Roboto', system-ui, sans-serif;
--font-tmd02-mono: 'Roboto Mono', ui-monospace, monospace;
--font-tmd02-icon: 'Material Symbols Outlined';
--shadow-tmd02-1: 0 1px 2px 0 rgb(0 0 0 / 0.3), 0 1px 3px 1px rgb(0 0 0 / 0.15);
--shadow-tmd02-2: 0 1px 2px 0 rgb(0 0 0 / 0.3), 0 2px 6px 2px rgb(0 0 0 / 0.15);
--shadow-tmd02-3: 0 4px 8px 3px rgb(0 0 0 / 0.15), 0 1px 3px 0 rgb(0 0 0 / 0.3);
}
.tmd-02 {
width: 100%;
min-height: 100svh;
}
.tmd-02 [class*="font-tmd02-icon"] {
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
font-feature-settings: 'liga';
}@import "tailwindcss";
@theme {
--color-tmd02-bg: #eceff1;
--color-tmd02-bg: oklch(0.95 0.004 236.5);
--color-tmd02-surface: #fffbfe;
--color-tmd02-surface: oklch(0.992 0.006 334);
--color-tmd02-surfacevariant: #e7e0ec;
--color-tmd02-surfacevariant: oklch(0.916 0.018 311.6);
--color-tmd02-primary: #1565c0;
--color-tmd02-primary: oklch(0.513 0.16 255.7);
--color-tmd02-onprimary: #ffffff;
--color-tmd02-onprimary: oklch(1 0 89.9);
--color-tmd02-onsurface: #1c1b1f;
--color-tmd02-onsurface: oklch(0.225 0.008 297.2);
--color-tmd02-onvariant: #49454f;
--color-tmd02-onvariant: oklch(0.398 0.017 303.7);
--color-tmd02-outline: #79747e;
--color-tmd02-outline: oklch(0.567 0.016 308.1);
--color-tmd02-outlinevariant: #cac4d0;
--color-tmd02-outlinevariant: oklch(0.829 0.018 308.2);
--font-tmd02-sans: 'Roboto Flex', 'Roboto', system-ui, sans-serif;
--font-tmd02-mono: 'Roboto Mono', ui-monospace, monospace;
--font-tmd02-icon: 'Material Symbols Outlined';
--shadow-tmd02-1: 0 1px 2px 0 rgb(0 0 0 / 0.3), 0 1px 3px 1px rgb(0 0 0 / 0.15);
--shadow-tmd02-2: 0 1px 2px 0 rgb(0 0 0 / 0.3), 0 2px 6px 2px rgb(0 0 0 / 0.15);
--shadow-tmd02-3: 0 4px 8px 3px rgb(0 0 0 / 0.15), 0 1px 3px 0 rgb(0 0 0 / 0.3);
}
.tmd-02 {
width: 100%;
min-height: 100svh;
}
.tmd-02 [class*="font-tmd02-icon"] {
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
font-feature-settings: 'liga';
}<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto+Flex:opsz,wght@8..144,300..700&family=Roboto+Mono:wght@400;500&family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,400,0..1,0&display=swap">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto+Flex:opsz,wght@8..144,300..700&family=Roboto+Mono:wght@400;500&family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,400,0..1,0&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
- Three
.card:hover .xrules becomegroupon the article and onegroup-hover:utility on each responder, which is the single most reusable idea in the whole collection. focus-within:is added as the keyboard twin of every card hover — the CSS version had no equivalent, and it costs one utility here.- The responsive grid needs no
@mediaat all:minmax(min(260px,100%),1fr)insideauto-fitis the arbitrary value that replaces two breakpoints. - Card variants stop being classes. “Elevated” is
shadow-tmd02-1, “filled” isbg-tmd02-surfacevariant, “outlined” isborder— each a property, stated where it applies. - The action buttons repeat demo 01's state-layer recipe verbatim. Repetition across cards is the honest cost of utilities; a component wrapper is the fix in a real codebase, not
@apply.
Here's a working CSS demo from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Tailwind Material Design 3 Cards
Source: https://codefronts.com/design-styles/tailwind-material-design-components/tailwind-material-design-3-cards/
The three M3 card containers — elevated, filled and outlined — plus a horizontal media card and a stat tile, laid out on a grid that reflows with no media query. The port's real gain is group-hover:: the whole card is the hover target, so the title colour, the elevation and the media zoom all respond from one hovered ancestor instead of three descendant rules.
## HTML
```html
<section class="tmd-02 w-full min-h-svh bg-tmd02-bg font-tmd02-sans text-tmd02-onsurface px-5 py-9" aria-label="Material Design 3 cards demo">
<div class="w-[min(1100px,100%)] mx-auto grid gap-6">
<header class="grid gap-1.5">
<h2 class="text-[26px] font-medium tracking-[-0.01em]">Material Design 3 cards</h2>
<p class="text-sm text-tmd02-onvariant">Elevated, filled and outlined containers — one <code class="font-tmd02-mono text-[12px]">group</code> per card drives every hover response.</p>
</header>
<div class="grid gap-4 grid-cols-[repeat(auto-fit,minmax(min(260px,100%),1fr))]">
<article class="group grid rounded-xl bg-tmd02-surface shadow-tmd02-1 transition-shadow duration-250 hover:shadow-tmd02-3 focus-within:shadow-tmd02-3 motion-reduce:transition-none">
<div class="overflow-hidden rounded-t-xl">
<div class="aspect-[16/10] bg-linear-[145deg,#1565c0,#42a5f5_70%,#90caf9] transition-transform duration-400 group-hover:scale-105 motion-reduce:transition-none" role="img" aria-label="Coastal route illustration"></div>
</div>
<div class="grid gap-1.5 p-4">
<span class="font-tmd02-mono text-[11px] uppercase tracking-[0.1em] text-tmd02-onvariant">Elevated</span>
<h3 class="text-lg font-medium tracking-[-0.005em] transition-colors duration-200 group-hover:text-tmd02-primary"><a href="#route" class="text-inherit no-underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tmd02-primary rounded-sm">Coastal route, revisited</a></h3>
<p class="text-sm leading-6 text-tmd02-onvariant text-pretty line-clamp-2">Fourteen kilometres of cliff path, mapped for the first time since the 1998 survey.</p>
</div>
<div class="flex gap-2 px-3 pb-3">
<button type="button" class="relative overflow-hidden min-h-10 px-4 rounded-full border-0 bg-transparent cursor-pointer text-sm font-medium text-tmd02-primary focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tmd02-primary before:absolute before:inset-0 before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08] active:before:opacity-[0.12]">Open</button>
<button type="button" class="relative overflow-hidden min-h-10 px-4 rounded-full border border-tmd02-outline bg-transparent cursor-pointer text-sm font-medium text-tmd02-primary focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tmd02-primary before:absolute before:inset-0 before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08] active:before:opacity-[0.12]">Share</button>
</div>
</article>
<article class="group grid rounded-xl bg-tmd02-surfacevariant">
<div class="overflow-hidden rounded-t-xl">
<div class="aspect-[16/10] bg-linear-[145deg,#00695c,#26a69a_70%,#b2dfdb] transition-transform duration-400 group-hover:scale-105 motion-reduce:transition-none" role="img" aria-label="Field station illustration"></div>
</div>
<div class="grid gap-1.5 p-4">
<span class="font-tmd02-mono text-[11px] uppercase tracking-[0.1em] text-tmd02-onvariant">Filled</span>
<h3 class="text-lg font-medium tracking-[-0.005em] transition-colors duration-200 group-hover:text-tmd02-primary">Station 4 telemetry</h3>
<p class="text-sm leading-6 text-tmd02-onvariant text-pretty line-clamp-2">Filled cards sit on surface-variant with no shadow — the flattest of the three containers.</p>
</div>
<div class="flex items-center gap-1 px-3 pb-3">
<button type="button" aria-label="Add to favourites" class="relative overflow-hidden grid place-items-center min-w-11 min-h-11 rounded-full border-0 bg-transparent cursor-pointer text-tmd02-onvariant focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tmd02-primary before:absolute before:inset-1 before:rounded-full before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08]"><span class="font-tmd02-icon text-[20px] leading-none" aria-hidden="true">favorite</span></button>
<button type="button" aria-label="Download" class="relative overflow-hidden grid place-items-center min-w-11 min-h-11 rounded-full border-0 bg-transparent cursor-pointer text-tmd02-onvariant focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tmd02-primary before:absolute before:inset-1 before:rounded-full before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08]"><span class="font-tmd02-icon text-[20px] leading-none" aria-hidden="true">download</span></button>
<span class="ml-auto self-center font-tmd02-mono text-[11px] text-tmd02-onvariant">12.4 MB</span>
</div>
</article>
<article class="group grid rounded-xl bg-tmd02-surface border border-tmd02-outlinevariant transition-colors duration-200 hover:border-tmd02-primary">
<div class="grid gap-1.5 p-4">
<span class="font-tmd02-mono text-[11px] uppercase tracking-[0.1em] text-tmd02-onvariant">Outlined</span>
<h3 class="text-lg font-medium tracking-[-0.005em]">No shadow, one hairline</h3>
<p class="text-sm leading-6 text-tmd02-onvariant text-pretty">Outlined is the accessible default on busy backgrounds: a 1px <span class="font-tmd02-mono text-[12px]">outline-variant</span> border reads at any elevation, and there is nothing to repaint on hover.</p>
</div>
<dl class="grid grid-cols-3 gap-2 px-4 pb-4 m-0">
<div class="grid gap-0.5"><dt class="font-tmd02-mono text-[10px] uppercase tracking-[0.08em] text-tmd02-onvariant">Sites</dt><dd class="m-0 text-xl font-medium tabular-nums">18</dd></div>
<div class="grid gap-0.5"><dt class="font-tmd02-mono text-[10px] uppercase tracking-[0.08em] text-tmd02-onvariant">Depth</dt><dd class="m-0 text-xl font-medium tabular-nums">240m</dd></div>
<div class="grid gap-0.5"><dt class="font-tmd02-mono text-[10px] uppercase tracking-[0.08em] text-tmd02-onvariant">Uptime</dt><dd class="m-0 text-xl font-medium tabular-nums">99.1%</dd></div>
</dl>
</article>
</div>
<article class="group flex flex-wrap items-stretch gap-0 rounded-xl overflow-hidden bg-tmd02-surface shadow-tmd02-1 transition-shadow duration-250 hover:shadow-tmd02-2 motion-reduce:transition-none">
<div class="w-[min(200px,40%)] min-h-40 bg-linear-[150deg,#7b1fa2,#ba68c8_65%,#f8bbd0] transition-transform duration-400 group-hover:scale-105 motion-reduce:transition-none" role="img" aria-label="Album artwork"></div>
<div class="flex-1 min-w-[min(260px,100%)] grid content-center gap-2 p-5">
<span class="font-tmd02-mono text-[11px] uppercase tracking-[0.1em] text-tmd02-onvariant">Horizontal · media leading</span>
<h3 class="text-[21px] font-medium tracking-[-0.01em]">Tidal — deluxe edition</h3>
<p class="text-sm leading-6 text-tmd02-onvariant text-pretty">A horizontal card is the same container with <span class="font-tmd02-mono text-[12px]">flex-wrap</span> and a percentage media rail. Below 460px the rail wraps above the text with no breakpoint involved.</p>
<div class="flex gap-2 mt-1">
<button type="button" class="relative overflow-hidden inline-flex items-center gap-2 min-h-10 px-5 rounded-full border-0 bg-tmd02-primary text-tmd02-onprimary cursor-pointer text-sm font-medium focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tmd02-primary before:absolute before:inset-0 before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08] active:before:opacity-[0.12]"><span class="font-tmd02-icon text-[18px] leading-none" aria-hidden="true">play_arrow</span>Play</button>
<button type="button" class="relative overflow-hidden min-h-10 px-4 rounded-full border-0 bg-transparent cursor-pointer text-sm font-medium text-tmd02-primary focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tmd02-primary before:absolute before:inset-0 before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08]">Add to library</button>
</div>
</div>
</article>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tmd02-bg: #eceff1;
--color-tmd02-bg: oklch(0.95 0.004 236.5);
--color-tmd02-surface: #fffbfe;
--color-tmd02-surface: oklch(0.992 0.006 334);
--color-tmd02-surfacevariant: #e7e0ec;
--color-tmd02-surfacevariant: oklch(0.916 0.018 311.6);
--color-tmd02-primary: #1565c0;
--color-tmd02-primary: oklch(0.513 0.16 255.7);
--color-tmd02-onprimary: #ffffff;
--color-tmd02-onprimary: oklch(1 0 89.9);
--color-tmd02-onsurface: #1c1b1f;
--color-tmd02-onsurface: oklch(0.225 0.008 297.2);
--color-tmd02-onvariant: #49454f;
--color-tmd02-onvariant: oklch(0.398 0.017 303.7);
--color-tmd02-outline: #79747e;
--color-tmd02-outline: oklch(0.567 0.016 308.1);
--color-tmd02-outlinevariant: #cac4d0;
--color-tmd02-outlinevariant: oklch(0.829 0.018 308.2);
--font-tmd02-sans: 'Roboto Flex', 'Roboto', system-ui, sans-serif;
--font-tmd02-mono: 'Roboto Mono', ui-monospace, monospace;
--font-tmd02-icon: 'Material Symbols Outlined';
--shadow-tmd02-1: 0 1px 2px 0 rgb(0 0 0 / 0.3), 0 1px 3px 1px rgb(0 0 0 / 0.15);
--shadow-tmd02-2: 0 1px 2px 0 rgb(0 0 0 / 0.3), 0 2px 6px 2px rgb(0 0 0 / 0.15);
--shadow-tmd02-3: 0 4px 8px 3px rgb(0 0 0 / 0.15), 0 1px 3px 0 rgb(0 0 0 / 0.3);
}
.tmd-02 {
width: 100%;
min-height: 100svh;
}
.tmd-02 [class*="font-tmd02-icon"] {
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
font-feature-settings: 'liga';
}
```Here's a working CSS demo from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Tailwind Material Design 3 Cards
Source: https://codefronts.com/design-styles/tailwind-material-design-components/tailwind-material-design-3-cards/
The three M3 card containers — elevated, filled and outlined — plus a horizontal media card and a stat tile, laid out on a grid that reflows with no media query. The port's real gain is group-hover:: the whole card is the hover target, so the title colour, the elevation and the media zoom all respond from one hovered ancestor instead of three descendant rules.
## HTML
```html
<section class="tmd-02 w-full min-h-svh bg-tmd02-bg font-tmd02-sans text-tmd02-onsurface px-5 py-9" aria-label="Material Design 3 cards demo">
<div class="w-[min(1100px,100%)] mx-auto grid gap-6">
<header class="grid gap-1.5">
<h2 class="text-[26px] font-medium tracking-[-0.01em]">Material Design 3 cards</h2>
<p class="text-sm text-tmd02-onvariant">Elevated, filled and outlined containers — one <code class="font-tmd02-mono text-[12px]">group</code> per card drives every hover response.</p>
</header>
<div class="grid gap-4 grid-cols-[repeat(auto-fit,minmax(min(260px,100%),1fr))]">
<article class="group grid rounded-xl bg-tmd02-surface shadow-tmd02-1 transition-shadow duration-250 hover:shadow-tmd02-3 focus-within:shadow-tmd02-3 motion-reduce:transition-none">
<div class="overflow-hidden rounded-t-xl">
<div class="aspect-[16/10] bg-linear-[145deg,#1565c0,#42a5f5_70%,#90caf9] transition-transform duration-400 group-hover:scale-105 motion-reduce:transition-none" role="img" aria-label="Coastal route illustration"></div>
</div>
<div class="grid gap-1.5 p-4">
<span class="font-tmd02-mono text-[11px] uppercase tracking-[0.1em] text-tmd02-onvariant">Elevated</span>
<h3 class="text-lg font-medium tracking-[-0.005em] transition-colors duration-200 group-hover:text-tmd02-primary"><a href="#route" class="text-inherit no-underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tmd02-primary rounded-sm">Coastal route, revisited</a></h3>
<p class="text-sm leading-6 text-tmd02-onvariant text-pretty line-clamp-2">Fourteen kilometres of cliff path, mapped for the first time since the 1998 survey.</p>
</div>
<div class="flex gap-2 px-3 pb-3">
<button type="button" class="relative overflow-hidden min-h-10 px-4 rounded-full border-0 bg-transparent cursor-pointer text-sm font-medium text-tmd02-primary focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tmd02-primary before:absolute before:inset-0 before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08] active:before:opacity-[0.12]">Open</button>
<button type="button" class="relative overflow-hidden min-h-10 px-4 rounded-full border border-tmd02-outline bg-transparent cursor-pointer text-sm font-medium text-tmd02-primary focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tmd02-primary before:absolute before:inset-0 before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08] active:before:opacity-[0.12]">Share</button>
</div>
</article>
<article class="group grid rounded-xl bg-tmd02-surfacevariant">
<div class="overflow-hidden rounded-t-xl">
<div class="aspect-[16/10] bg-linear-[145deg,#00695c,#26a69a_70%,#b2dfdb] transition-transform duration-400 group-hover:scale-105 motion-reduce:transition-none" role="img" aria-label="Field station illustration"></div>
</div>
<div class="grid gap-1.5 p-4">
<span class="font-tmd02-mono text-[11px] uppercase tracking-[0.1em] text-tmd02-onvariant">Filled</span>
<h3 class="text-lg font-medium tracking-[-0.005em] transition-colors duration-200 group-hover:text-tmd02-primary">Station 4 telemetry</h3>
<p class="text-sm leading-6 text-tmd02-onvariant text-pretty line-clamp-2">Filled cards sit on surface-variant with no shadow — the flattest of the three containers.</p>
</div>
<div class="flex items-center gap-1 px-3 pb-3">
<button type="button" aria-label="Add to favourites" class="relative overflow-hidden grid place-items-center min-w-11 min-h-11 rounded-full border-0 bg-transparent cursor-pointer text-tmd02-onvariant focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tmd02-primary before:absolute before:inset-1 before:rounded-full before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08]"><span class="font-tmd02-icon text-[20px] leading-none" aria-hidden="true">favorite</span></button>
<button type="button" aria-label="Download" class="relative overflow-hidden grid place-items-center min-w-11 min-h-11 rounded-full border-0 bg-transparent cursor-pointer text-tmd02-onvariant focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tmd02-primary before:absolute before:inset-1 before:rounded-full before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08]"><span class="font-tmd02-icon text-[20px] leading-none" aria-hidden="true">download</span></button>
<span class="ml-auto self-center font-tmd02-mono text-[11px] text-tmd02-onvariant">12.4 MB</span>
</div>
</article>
<article class="group grid rounded-xl bg-tmd02-surface border border-tmd02-outlinevariant transition-colors duration-200 hover:border-tmd02-primary">
<div class="grid gap-1.5 p-4">
<span class="font-tmd02-mono text-[11px] uppercase tracking-[0.1em] text-tmd02-onvariant">Outlined</span>
<h3 class="text-lg font-medium tracking-[-0.005em]">No shadow, one hairline</h3>
<p class="text-sm leading-6 text-tmd02-onvariant text-pretty">Outlined is the accessible default on busy backgrounds: a 1px <span class="font-tmd02-mono text-[12px]">outline-variant</span> border reads at any elevation, and there is nothing to repaint on hover.</p>
</div>
<dl class="grid grid-cols-3 gap-2 px-4 pb-4 m-0">
<div class="grid gap-0.5"><dt class="font-tmd02-mono text-[10px] uppercase tracking-[0.08em] text-tmd02-onvariant">Sites</dt><dd class="m-0 text-xl font-medium tabular-nums">18</dd></div>
<div class="grid gap-0.5"><dt class="font-tmd02-mono text-[10px] uppercase tracking-[0.08em] text-tmd02-onvariant">Depth</dt><dd class="m-0 text-xl font-medium tabular-nums">240m</dd></div>
<div class="grid gap-0.5"><dt class="font-tmd02-mono text-[10px] uppercase tracking-[0.08em] text-tmd02-onvariant">Uptime</dt><dd class="m-0 text-xl font-medium tabular-nums">99.1%</dd></div>
</dl>
</article>
</div>
<article class="group flex flex-wrap items-stretch gap-0 rounded-xl overflow-hidden bg-tmd02-surface shadow-tmd02-1 transition-shadow duration-250 hover:shadow-tmd02-2 motion-reduce:transition-none">
<div class="w-[min(200px,40%)] min-h-40 bg-linear-[150deg,#7b1fa2,#ba68c8_65%,#f8bbd0] transition-transform duration-400 group-hover:scale-105 motion-reduce:transition-none" role="img" aria-label="Album artwork"></div>
<div class="flex-1 min-w-[min(260px,100%)] grid content-center gap-2 p-5">
<span class="font-tmd02-mono text-[11px] uppercase tracking-[0.1em] text-tmd02-onvariant">Horizontal · media leading</span>
<h3 class="text-[21px] font-medium tracking-[-0.01em]">Tidal — deluxe edition</h3>
<p class="text-sm leading-6 text-tmd02-onvariant text-pretty">A horizontal card is the same container with <span class="font-tmd02-mono text-[12px]">flex-wrap</span> and a percentage media rail. Below 460px the rail wraps above the text with no breakpoint involved.</p>
<div class="flex gap-2 mt-1">
<button type="button" class="relative overflow-hidden inline-flex items-center gap-2 min-h-10 px-5 rounded-full border-0 bg-tmd02-primary text-tmd02-onprimary cursor-pointer text-sm font-medium focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tmd02-primary before:absolute before:inset-0 before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08] active:before:opacity-[0.12]"><span class="font-tmd02-icon text-[18px] leading-none" aria-hidden="true">play_arrow</span>Play</button>
<button type="button" class="relative overflow-hidden min-h-10 px-4 rounded-full border-0 bg-transparent cursor-pointer text-sm font-medium text-tmd02-primary focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tmd02-primary before:absolute before:inset-0 before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08]">Add to library</button>
</div>
</div>
</article>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tmd02-bg: #eceff1;
--color-tmd02-bg: oklch(0.95 0.004 236.5);
--color-tmd02-surface: #fffbfe;
--color-tmd02-surface: oklch(0.992 0.006 334);
--color-tmd02-surfacevariant: #e7e0ec;
--color-tmd02-surfacevariant: oklch(0.916 0.018 311.6);
--color-tmd02-primary: #1565c0;
--color-tmd02-primary: oklch(0.513 0.16 255.7);
--color-tmd02-onprimary: #ffffff;
--color-tmd02-onprimary: oklch(1 0 89.9);
--color-tmd02-onsurface: #1c1b1f;
--color-tmd02-onsurface: oklch(0.225 0.008 297.2);
--color-tmd02-onvariant: #49454f;
--color-tmd02-onvariant: oklch(0.398 0.017 303.7);
--color-tmd02-outline: #79747e;
--color-tmd02-outline: oklch(0.567 0.016 308.1);
--color-tmd02-outlinevariant: #cac4d0;
--color-tmd02-outlinevariant: oklch(0.829 0.018 308.2);
--font-tmd02-sans: 'Roboto Flex', 'Roboto', system-ui, sans-serif;
--font-tmd02-mono: 'Roboto Mono', ui-monospace, monospace;
--font-tmd02-icon: 'Material Symbols Outlined';
--shadow-tmd02-1: 0 1px 2px 0 rgb(0 0 0 / 0.3), 0 1px 3px 1px rgb(0 0 0 / 0.15);
--shadow-tmd02-2: 0 1px 2px 0 rgb(0 0 0 / 0.3), 0 2px 6px 2px rgb(0 0 0 / 0.15);
--shadow-tmd02-3: 0 4px 8px 3px rgb(0 0 0 / 0.15), 0 1px 3px 0 rgb(0 0 0 / 0.3);
}
.tmd-02 {
width: 100%;
min-height: 100svh;
}
.tmd-02 [class*="font-tmd02-icon"] {
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
font-feature-settings: 'liga';
}
```How this works
One hover source, many responders. The CSS collection writes .card:hover .title, .card:hover .media img and .card:hover as three rules. Utilities invert that: the card is marked group, and each child states how it answers.
<article class="group rounded-xl shadow-tmd02-1 hover:shadow-tmd02-3">
<div class="overflow-hidden">
<div class="aspect-[16/10] transition-transform group-hover:scale-105"></div>
</div>
<h3 class="group-hover:text-tmd02-primary">…</h3>The three containers are three lines, not three blocks. Elevated is bg-tmd02-surface shadow-tmd02-1; filled is bg-tmd02-surfacevariant with no shadow; outlined is border border-tmd02-outlinevariant. Material's own spec distinguishes them by exactly those properties, so the class list reads as the spec.
Media without images. Each media block is a gradient so the demo has no external dependency; swap in an <img class="w-full h-full object-cover"> and nothing about the layout changes, because the aspect-* box owns the geometry.
Make it yours
- Card density: the single
p-4on the body andgap-4on the grid control the whole rhythm. - Corner radius: M3 medium is 12px (
rounded-xl); gorounded-3xlfor an expressive theme, and the media block inherits the clip fromoverflow-hidden. - Turn the hover lift into a translate by adding
group-hover:-translate-y-1next to the shadow swap; keep themotion-reduce:twin. - Replace the gradient media with real images — keep
object-coverand theaspect-*box. - Drop the stat tile if the collection only needs content cards; it is the one card here that is a dashboard pattern rather than a content pattern.
Gotchas — read before shipping
- A whole-card
<a>wrapper plus action buttons inside is invalid nesting. Keep the card a plain container and let the title link and the buttons be separate targets. overflow-hiddenbelongs on the media wrapper, not the card, or the card's own focus ring gets clipped.group-hover:on a touch device fires on tap and can stick; treat the hover lift as decoration and never hide information behind it.- Do not transition
box-shadowtogether withtransformon the same element withoutwill-changediscipline; shadow repaints are the expensive half of a card hover. line-clamp-2needs a defined line height to be predictable — theleading-*utility is part of the clamp, not decoration.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
aspect-ratio and line-clamp are Baseline; the floor is Tailwind v4's generated sheet.