14 / 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 Responsive App Shell Layout
The whole thing assembled: a sticky top app bar that gains elevation on scroll, a navigation rail that becomes a modal drawer under md, and a dashboard grid of the earlier demos' components. Every M3 layout breakpoint is a Tailwind responsive modifier, and the only JavaScript is the drawer's focus management and one scroll listener.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section class="tmd-14 w-full min-h-svh bg-tmd14-bg font-tmd14-sans text-tmd14-onsurface" aria-label="Material Design app shell demo">
<div class="relative grid md:grid-cols-[80px_1fr] lg:grid-cols-[256px_1fr] min-h-svh">
<nav aria-label="Main" class="hidden md:grid content-start gap-1 py-3 px-2 bg-tmd14-surface2 border-r border-tmd14-outlinevariant">
<span class="hidden lg:block px-4 py-3 font-tmd14-mono text-[11px] uppercase tracking-[0.12em] text-tmd14-onvariant">Hydrolab</span>
<a href="#dash" aria-current="page" class="relative overflow-hidden grid lg:grid-cols-[auto_1fr] justify-items-center lg:justify-items-start items-center gap-1 lg:gap-3 min-h-14 px-2 lg:px-4 rounded-full no-underline text-[11px] lg:text-sm font-medium text-tmd14-onvariant transition-colors duration-200 aria-[current=page]:bg-tmd14-secondarycontainer aria-[current=page]:text-tmd14-onsecondarycontainer focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tmd14-primary before:absolute before:inset-0 before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08]"><span class="font-tmd14-icon text-[22px] leading-none" aria-hidden="true">dashboard</span>Overview</a>
<a href="#stations" class="relative overflow-hidden grid lg:grid-cols-[auto_1fr] justify-items-center lg:justify-items-start items-center gap-1 lg:gap-3 min-h-14 px-2 lg:px-4 rounded-full no-underline text-[11px] lg:text-sm font-medium text-tmd14-onvariant transition-colors duration-200 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tmd14-primary before:absolute before:inset-0 before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08]"><span class="font-tmd14-icon text-[22px] leading-none" aria-hidden="true">sensors</span>Stations</a>
<a href="#reports" class="relative overflow-hidden grid lg:grid-cols-[auto_1fr] justify-items-center lg:justify-items-start items-center gap-1 lg:gap-3 min-h-14 px-2 lg:px-4 rounded-full no-underline text-[11px] lg:text-sm font-medium text-tmd14-onvariant transition-colors duration-200 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tmd14-primary before:absolute before:inset-0 before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08]"><span class="font-tmd14-icon text-[22px] leading-none" aria-hidden="true">bar_chart</span>Reports</a>
<a href="#team" class="relative overflow-hidden grid lg:grid-cols-[auto_1fr] justify-items-center lg:justify-items-start items-center gap-1 lg:gap-3 min-h-14 px-2 lg:px-4 rounded-full no-underline text-[11px] lg:text-sm font-medium text-tmd14-onvariant transition-colors duration-200 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tmd14-primary before:absolute before:inset-0 before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08]"><span class="font-tmd14-icon text-[22px] leading-none" aria-hidden="true">group</span>Team</a>
</nav>
<div id="tmd14-main" class="grid grid-rows-[auto_1fr] min-w-0">
<header data-bar class="sticky top-0 z-20 flex items-center gap-2 h-16 px-3 bg-tmd14-surface/85 backdrop-blur-sm border-b border-transparent transition-[box-shadow,border-color] duration-200 data-scrolled:shadow-tmd14-2 data-scrolled:border-tmd14-outlinevariant motion-reduce:transition-none">
<button type="button" data-drawer-open aria-expanded="false" aria-controls="tmd14-drawer" aria-label="Open navigation" class="md:hidden relative overflow-hidden grid place-items-center min-w-11 min-h-11 rounded-full border-0 bg-transparent cursor-pointer text-tmd14-onsurface focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tmd14-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-tmd14-icon text-[22px] leading-none" aria-hidden="true">menu</span></button>
<h2 class="text-[17px] font-medium">Overview</h2>
<div class="ml-auto flex items-center gap-1">
<button type="button" aria-label="Search" class="relative overflow-hidden grid place-items-center min-w-11 min-h-11 rounded-full border-0 bg-transparent cursor-pointer text-tmd14-onvariant focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tmd14-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-tmd14-icon text-[22px] leading-none" aria-hidden="true">search</span></button>
<span class="relative inline-block"><button type="button" aria-label="Alerts, 3 unread" class="relative overflow-hidden grid place-items-center min-w-11 min-h-11 rounded-full border-0 bg-transparent cursor-pointer text-tmd14-onvariant focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tmd14-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-tmd14-icon text-[22px] leading-none" aria-hidden="true">notifications</span></button><span class="absolute top-1 right-1 min-w-4 h-4 px-1 grid place-items-center rounded-full bg-tmd14-error text-white font-tmd14-mono text-[10px] pointer-events-none" aria-hidden="true">3</span></span>
<span class="grid place-items-center w-9 h-9 ml-1 rounded-full bg-tmd14-primary text-white font-tmd14-mono text-xs" aria-hidden="true">MK</span>
</div>
</header>
<main class="grid content-start gap-4 p-4 md:p-6">
<div class="grid gap-4 grid-cols-[repeat(auto-fit,minmax(min(240px,100%),1fr))]">
<article class="grid gap-1 p-5 rounded-3xl bg-tmd14-surface shadow-tmd14-1"><span class="font-tmd14-mono text-[11px] uppercase tracking-[0.08em] text-tmd14-onvariant">Stations online</span><b class="text-[32px] font-medium leading-tight tabular-nums">16<span class="text-base text-tmd14-onvariant"> / 18</span></b><div class="h-1 mt-2 rounded-full bg-tmd14-outlinevariant overflow-hidden"><div class="h-full w-[89%] rounded-full bg-tmd14-primary"></div></div></article>
<article class="grid gap-1 p-5 rounded-3xl bg-tmd14-surface shadow-tmd14-1"><span class="font-tmd14-mono text-[11px] uppercase tracking-[0.08em] text-tmd14-onvariant">Readings today</span><b class="text-[32px] font-medium leading-tight tabular-nums">4 210</b><span class="text-xs text-tmd14-onvariant">+6.2% on yesterday</span></article>
<article class="grid gap-1 p-5 rounded-3xl bg-tmd14-secondarycontainer text-tmd14-onsecondarycontainer"><span class="font-tmd14-mono text-[11px] uppercase tracking-[0.08em] opacity-80">Open alerts</span><b class="text-[32px] font-medium leading-tight tabular-nums">3</b><span class="text-xs opacity-80">1 offline · 2 drift</span></article>
</div>
<div class="grid gap-4 lg:grid-cols-[1.4fr_1fr]">
<article class="grid content-start gap-3 p-5 rounded-3xl bg-tmd14-surface shadow-tmd14-1">
<div class="flex items-baseline justify-between gap-3"><b class="text-[15px] font-medium">Depth coverage</b><span class="font-tmd14-mono text-[11px] text-tmd14-onvariant">last 7 days</span></div>
<div class="grid grid-cols-7 items-end gap-2 h-40" role="img" aria-label="Bar chart of depth coverage over seven days">
<div class="h-[62%] rounded-t-lg bg-tmd14-primary/85"></div>
<div class="h-[74%] rounded-t-lg bg-tmd14-primary/85"></div>
<div class="h-[48%] rounded-t-lg bg-tmd14-primary/85"></div>
<div class="h-[86%] rounded-t-lg bg-tmd14-primary/85"></div>
<div class="h-[70%] rounded-t-lg bg-tmd14-primary/85"></div>
<div class="h-[92%] rounded-t-lg bg-tmd14-primary"></div>
<div class="h-[58%] rounded-t-lg bg-tmd14-primary/40"></div>
</div>
</article>
<article class="grid content-start gap-2 p-5 rounded-3xl bg-tmd14-surface shadow-tmd14-1">
<b class="text-[15px] font-medium">Recent activity</b>
<ul role="list" class="list-none m-0 p-0 divide-y divide-tmd14-outlinevariant">
<li class="grid grid-cols-[auto_1fr_auto] items-center gap-3 min-h-14"><span class="grid place-items-center w-9 h-9 rounded-full bg-tmd14-surface2 font-tmd14-icon text-[18px] leading-none text-tmd14-onvariant" aria-hidden="true">warning</span><span class="min-w-0 text-sm truncate">ST-047 went offline</span><small class="font-tmd14-mono text-[11px] text-tmd14-onvariant">12m</small></li>
<li class="grid grid-cols-[auto_1fr_auto] items-center gap-3 min-h-14"><span class="grid place-items-center w-9 h-9 rounded-full bg-tmd14-surface2 font-tmd14-icon text-[18px] leading-none text-tmd14-onvariant" aria-hidden="true">check_circle</span><span class="min-w-0 text-sm truncate">ST-031 recalibrated</span><small class="font-tmd14-mono text-[11px] text-tmd14-onvariant">1h</small></li>
<li class="grid grid-cols-[auto_1fr_auto] items-center gap-3 min-h-14"><span class="grid place-items-center w-9 h-9 rounded-full bg-tmd14-surface2 font-tmd14-icon text-[18px] leading-none text-tmd14-onvariant" aria-hidden="true">cloud_done</span><span class="min-w-0 text-sm truncate">Nightly sync complete</span><small class="font-tmd14-mono text-[11px] text-tmd14-onvariant">6h</small></li>
</ul>
</article>
</div>
<p class="text-xs text-tmd14-onvariant">Resize the frame: single column below <span class="font-tmd14-mono">md</span>, 80px rail at <span class="font-tmd14-mono">md</span>, 256px drawer at <span class="font-tmd14-mono">lg</span>. Scroll to elevate the bar.</p>
<div class="h-40" aria-hidden="true"></div>
</main>
</div>
<button type="button" data-scrim tabindex="-1" aria-label="Close navigation" class="md:hidden fixed inset-0 z-30 hidden border-0 bg-black/40 cursor-pointer data-open:block"></button>
<nav id="tmd14-drawer" aria-label="Navigation drawer" class="md:hidden fixed inset-y-0 left-0 z-40 grid content-start gap-1 w-70 py-3 px-2 bg-tmd14-surface2 [transform:translateX(-100%)] transition-transform duration-300 ease-out data-open:[transform:translateX(0)] motion-reduce:transition-none">
<div class="flex items-center gap-2 px-2 py-2">
<button type="button" data-drawer-close aria-label="Close navigation" class="relative overflow-hidden grid place-items-center min-w-11 min-h-11 rounded-full border-0 bg-transparent cursor-pointer text-tmd14-onsurface focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tmd14-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-tmd14-icon text-[22px] leading-none" aria-hidden="true">close</span></button>
<span class="font-tmd14-mono text-[11px] uppercase tracking-[0.12em] text-tmd14-onvariant">Hydrolab</span>
</div>
<a href="#dash" aria-current="page" class="relative overflow-hidden flex items-center gap-3 min-h-14 px-4 rounded-full no-underline text-sm font-medium text-tmd14-onvariant aria-[current=page]:bg-tmd14-secondarycontainer aria-[current=page]:text-tmd14-onsecondarycontainer focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tmd14-primary before:absolute before:inset-0 before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08]"><span class="font-tmd14-icon text-[22px] leading-none" aria-hidden="true">dashboard</span>Overview</a>
<a href="#stations" class="relative overflow-hidden flex items-center gap-3 min-h-14 px-4 rounded-full no-underline text-sm font-medium text-tmd14-onvariant focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tmd14-primary before:absolute before:inset-0 before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08]"><span class="font-tmd14-icon text-[22px] leading-none" aria-hidden="true">sensors</span>Stations</a>
<a href="#reports" class="relative overflow-hidden flex items-center gap-3 min-h-14 px-4 rounded-full no-underline text-sm font-medium text-tmd14-onvariant focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tmd14-primary before:absolute before:inset-0 before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08]"><span class="font-tmd14-icon text-[22px] leading-none" aria-hidden="true">bar_chart</span>Reports</a>
<a href="#team" class="relative overflow-hidden flex items-center gap-3 min-h-14 px-4 rounded-full no-underline text-sm font-medium text-tmd14-onvariant focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tmd14-primary before:absolute before:inset-0 before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08]"><span class="font-tmd14-icon text-[22px] leading-none" aria-hidden="true">group</span>Team</a>
</nav>
</div>
</section><section class="tmd-14 w-full min-h-svh bg-tmd14-bg font-tmd14-sans text-tmd14-onsurface" aria-label="Material Design app shell demo">
<div class="relative grid md:grid-cols-[80px_1fr] lg:grid-cols-[256px_1fr] min-h-svh">
<nav aria-label="Main" class="hidden md:grid content-start gap-1 py-3 px-2 bg-tmd14-surface2 border-r border-tmd14-outlinevariant">
<span class="hidden lg:block px-4 py-3 font-tmd14-mono text-[11px] uppercase tracking-[0.12em] text-tmd14-onvariant">Hydrolab</span>
<a href="#dash" aria-current="page" class="relative overflow-hidden grid lg:grid-cols-[auto_1fr] justify-items-center lg:justify-items-start items-center gap-1 lg:gap-3 min-h-14 px-2 lg:px-4 rounded-full no-underline text-[11px] lg:text-sm font-medium text-tmd14-onvariant transition-colors duration-200 aria-[current=page]:bg-tmd14-secondarycontainer aria-[current=page]:text-tmd14-onsecondarycontainer focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tmd14-primary before:absolute before:inset-0 before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08]"><span class="font-tmd14-icon text-[22px] leading-none" aria-hidden="true">dashboard</span>Overview</a>
<a href="#stations" class="relative overflow-hidden grid lg:grid-cols-[auto_1fr] justify-items-center lg:justify-items-start items-center gap-1 lg:gap-3 min-h-14 px-2 lg:px-4 rounded-full no-underline text-[11px] lg:text-sm font-medium text-tmd14-onvariant transition-colors duration-200 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tmd14-primary before:absolute before:inset-0 before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08]"><span class="font-tmd14-icon text-[22px] leading-none" aria-hidden="true">sensors</span>Stations</a>
<a href="#reports" class="relative overflow-hidden grid lg:grid-cols-[auto_1fr] justify-items-center lg:justify-items-start items-center gap-1 lg:gap-3 min-h-14 px-2 lg:px-4 rounded-full no-underline text-[11px] lg:text-sm font-medium text-tmd14-onvariant transition-colors duration-200 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tmd14-primary before:absolute before:inset-0 before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08]"><span class="font-tmd14-icon text-[22px] leading-none" aria-hidden="true">bar_chart</span>Reports</a>
<a href="#team" class="relative overflow-hidden grid lg:grid-cols-[auto_1fr] justify-items-center lg:justify-items-start items-center gap-1 lg:gap-3 min-h-14 px-2 lg:px-4 rounded-full no-underline text-[11px] lg:text-sm font-medium text-tmd14-onvariant transition-colors duration-200 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tmd14-primary before:absolute before:inset-0 before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08]"><span class="font-tmd14-icon text-[22px] leading-none" aria-hidden="true">group</span>Team</a>
</nav>
<div id="tmd14-main" class="grid grid-rows-[auto_1fr] min-w-0">
<header data-bar class="sticky top-0 z-20 flex items-center gap-2 h-16 px-3 bg-tmd14-surface/85 backdrop-blur-sm border-b border-transparent transition-[box-shadow,border-color] duration-200 data-scrolled:shadow-tmd14-2 data-scrolled:border-tmd14-outlinevariant motion-reduce:transition-none">
<button type="button" data-drawer-open aria-expanded="false" aria-controls="tmd14-drawer" aria-label="Open navigation" class="md:hidden relative overflow-hidden grid place-items-center min-w-11 min-h-11 rounded-full border-0 bg-transparent cursor-pointer text-tmd14-onsurface focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tmd14-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-tmd14-icon text-[22px] leading-none" aria-hidden="true">menu</span></button>
<h2 class="text-[17px] font-medium">Overview</h2>
<div class="ml-auto flex items-center gap-1">
<button type="button" aria-label="Search" class="relative overflow-hidden grid place-items-center min-w-11 min-h-11 rounded-full border-0 bg-transparent cursor-pointer text-tmd14-onvariant focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tmd14-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-tmd14-icon text-[22px] leading-none" aria-hidden="true">search</span></button>
<span class="relative inline-block"><button type="button" aria-label="Alerts, 3 unread" class="relative overflow-hidden grid place-items-center min-w-11 min-h-11 rounded-full border-0 bg-transparent cursor-pointer text-tmd14-onvariant focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tmd14-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-tmd14-icon text-[22px] leading-none" aria-hidden="true">notifications</span></button><span class="absolute top-1 right-1 min-w-4 h-4 px-1 grid place-items-center rounded-full bg-tmd14-error text-white font-tmd14-mono text-[10px] pointer-events-none" aria-hidden="true">3</span></span>
<span class="grid place-items-center w-9 h-9 ml-1 rounded-full bg-tmd14-primary text-white font-tmd14-mono text-xs" aria-hidden="true">MK</span>
</div>
</header>
<main class="grid content-start gap-4 p-4 md:p-6">
<div class="grid gap-4 grid-cols-[repeat(auto-fit,minmax(min(240px,100%),1fr))]">
<article class="grid gap-1 p-5 rounded-3xl bg-tmd14-surface shadow-tmd14-1"><span class="font-tmd14-mono text-[11px] uppercase tracking-[0.08em] text-tmd14-onvariant">Stations online</span><b class="text-[32px] font-medium leading-tight tabular-nums">16<span class="text-base text-tmd14-onvariant"> / 18</span></b><div class="h-1 mt-2 rounded-full bg-tmd14-outlinevariant overflow-hidden"><div class="h-full w-[89%] rounded-full bg-tmd14-primary"></div></div></article>
<article class="grid gap-1 p-5 rounded-3xl bg-tmd14-surface shadow-tmd14-1"><span class="font-tmd14-mono text-[11px] uppercase tracking-[0.08em] text-tmd14-onvariant">Readings today</span><b class="text-[32px] font-medium leading-tight tabular-nums">4 210</b><span class="text-xs text-tmd14-onvariant">+6.2% on yesterday</span></article>
<article class="grid gap-1 p-5 rounded-3xl bg-tmd14-secondarycontainer text-tmd14-onsecondarycontainer"><span class="font-tmd14-mono text-[11px] uppercase tracking-[0.08em] opacity-80">Open alerts</span><b class="text-[32px] font-medium leading-tight tabular-nums">3</b><span class="text-xs opacity-80">1 offline · 2 drift</span></article>
</div>
<div class="grid gap-4 lg:grid-cols-[1.4fr_1fr]">
<article class="grid content-start gap-3 p-5 rounded-3xl bg-tmd14-surface shadow-tmd14-1">
<div class="flex items-baseline justify-between gap-3"><b class="text-[15px] font-medium">Depth coverage</b><span class="font-tmd14-mono text-[11px] text-tmd14-onvariant">last 7 days</span></div>
<div class="grid grid-cols-7 items-end gap-2 h-40" role="img" aria-label="Bar chart of depth coverage over seven days">
<div class="h-[62%] rounded-t-lg bg-tmd14-primary/85"></div>
<div class="h-[74%] rounded-t-lg bg-tmd14-primary/85"></div>
<div class="h-[48%] rounded-t-lg bg-tmd14-primary/85"></div>
<div class="h-[86%] rounded-t-lg bg-tmd14-primary/85"></div>
<div class="h-[70%] rounded-t-lg bg-tmd14-primary/85"></div>
<div class="h-[92%] rounded-t-lg bg-tmd14-primary"></div>
<div class="h-[58%] rounded-t-lg bg-tmd14-primary/40"></div>
</div>
</article>
<article class="grid content-start gap-2 p-5 rounded-3xl bg-tmd14-surface shadow-tmd14-1">
<b class="text-[15px] font-medium">Recent activity</b>
<ul role="list" class="list-none m-0 p-0 divide-y divide-tmd14-outlinevariant">
<li class="grid grid-cols-[auto_1fr_auto] items-center gap-3 min-h-14"><span class="grid place-items-center w-9 h-9 rounded-full bg-tmd14-surface2 font-tmd14-icon text-[18px] leading-none text-tmd14-onvariant" aria-hidden="true">warning</span><span class="min-w-0 text-sm truncate">ST-047 went offline</span><small class="font-tmd14-mono text-[11px] text-tmd14-onvariant">12m</small></li>
<li class="grid grid-cols-[auto_1fr_auto] items-center gap-3 min-h-14"><span class="grid place-items-center w-9 h-9 rounded-full bg-tmd14-surface2 font-tmd14-icon text-[18px] leading-none text-tmd14-onvariant" aria-hidden="true">check_circle</span><span class="min-w-0 text-sm truncate">ST-031 recalibrated</span><small class="font-tmd14-mono text-[11px] text-tmd14-onvariant">1h</small></li>
<li class="grid grid-cols-[auto_1fr_auto] items-center gap-3 min-h-14"><span class="grid place-items-center w-9 h-9 rounded-full bg-tmd14-surface2 font-tmd14-icon text-[18px] leading-none text-tmd14-onvariant" aria-hidden="true">cloud_done</span><span class="min-w-0 text-sm truncate">Nightly sync complete</span><small class="font-tmd14-mono text-[11px] text-tmd14-onvariant">6h</small></li>
</ul>
</article>
</div>
<p class="text-xs text-tmd14-onvariant">Resize the frame: single column below <span class="font-tmd14-mono">md</span>, 80px rail at <span class="font-tmd14-mono">md</span>, 256px drawer at <span class="font-tmd14-mono">lg</span>. Scroll to elevate the bar.</p>
<div class="h-40" aria-hidden="true"></div>
</main>
</div>
<button type="button" data-scrim tabindex="-1" aria-label="Close navigation" class="md:hidden fixed inset-0 z-30 hidden border-0 bg-black/40 cursor-pointer data-open:block"></button>
<nav id="tmd14-drawer" aria-label="Navigation drawer" class="md:hidden fixed inset-y-0 left-0 z-40 grid content-start gap-1 w-70 py-3 px-2 bg-tmd14-surface2 [transform:translateX(-100%)] transition-transform duration-300 ease-out data-open:[transform:translateX(0)] motion-reduce:transition-none">
<div class="flex items-center gap-2 px-2 py-2">
<button type="button" data-drawer-close aria-label="Close navigation" class="relative overflow-hidden grid place-items-center min-w-11 min-h-11 rounded-full border-0 bg-transparent cursor-pointer text-tmd14-onsurface focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tmd14-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-tmd14-icon text-[22px] leading-none" aria-hidden="true">close</span></button>
<span class="font-tmd14-mono text-[11px] uppercase tracking-[0.12em] text-tmd14-onvariant">Hydrolab</span>
</div>
<a href="#dash" aria-current="page" class="relative overflow-hidden flex items-center gap-3 min-h-14 px-4 rounded-full no-underline text-sm font-medium text-tmd14-onvariant aria-[current=page]:bg-tmd14-secondarycontainer aria-[current=page]:text-tmd14-onsecondarycontainer focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tmd14-primary before:absolute before:inset-0 before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08]"><span class="font-tmd14-icon text-[22px] leading-none" aria-hidden="true">dashboard</span>Overview</a>
<a href="#stations" class="relative overflow-hidden flex items-center gap-3 min-h-14 px-4 rounded-full no-underline text-sm font-medium text-tmd14-onvariant focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tmd14-primary before:absolute before:inset-0 before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08]"><span class="font-tmd14-icon text-[22px] leading-none" aria-hidden="true">sensors</span>Stations</a>
<a href="#reports" class="relative overflow-hidden flex items-center gap-3 min-h-14 px-4 rounded-full no-underline text-sm font-medium text-tmd14-onvariant focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tmd14-primary before:absolute before:inset-0 before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08]"><span class="font-tmd14-icon text-[22px] leading-none" aria-hidden="true">bar_chart</span>Reports</a>
<a href="#team" class="relative overflow-hidden flex items-center gap-3 min-h-14 px-4 rounded-full no-underline text-sm font-medium text-tmd14-onvariant focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tmd14-primary before:absolute before:inset-0 before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08]"><span class="font-tmd14-icon text-[22px] leading-none" aria-hidden="true">group</span>Team</a>
</nav>
</div>
</section>@import "tailwindcss";
@theme {
--color-tmd14-bg: #eef1f6;
--color-tmd14-bg: oklch(0.957 0.007 260.7);
--color-tmd14-surface: #ffffff;
--color-tmd14-surface: oklch(1 0 89.9);
--color-tmd14-surface2: #f4f6fa;
--color-tmd14-surface2: oklch(0.973 0.006 264.5);
--color-tmd14-primary: #1565c0;
--color-tmd14-primary: oklch(0.513 0.16 255.7);
--color-tmd14-secondarycontainer: #dbe7f7;
--color-tmd14-secondarycontainer: oklch(0.924 0.025 255.6);
--color-tmd14-onsecondarycontainer: #0a2a4a;
--color-tmd14-onsecondarycontainer: oklch(0.281 0.07 251.8);
--color-tmd14-onsurface: #1c1b1f;
--color-tmd14-onsurface: oklch(0.225 0.008 297.2);
--color-tmd14-onvariant: #4d5966;
--color-tmd14-onvariant: oklch(0.459 0.026 250.5);
--color-tmd14-outlinevariant: #dde1e7;
--color-tmd14-outlinevariant: oklch(0.908 0.009 258.3);
--color-tmd14-error: #b3261e;
--color-tmd14-error: oklch(0.501 0.178 28.7);
--font-tmd14-sans: 'Roboto Flex', 'Roboto', system-ui, sans-serif;
--font-tmd14-mono: 'Roboto Mono', ui-monospace, monospace;
--font-tmd14-icon: 'Material Symbols Outlined';
--shadow-tmd14-1: 0 1px 2px 0 rgb(0 0 0 / 0.3), 0 1px 3px 1px rgb(0 0 0 / 0.15);
--shadow-tmd14-2: 0 1px 2px 0 rgb(0 0 0 / 0.3), 0 2px 6px 2px rgb(0 0 0 / 0.15);
}
.tmd-14 {
width: 100%;
min-height: 100svh;
}
.tmd-14 .font-tmd14-icon {
font-feature-settings: 'liga';
}@import "tailwindcss";
@theme {
--color-tmd14-bg: #eef1f6;
--color-tmd14-bg: oklch(0.957 0.007 260.7);
--color-tmd14-surface: #ffffff;
--color-tmd14-surface: oklch(1 0 89.9);
--color-tmd14-surface2: #f4f6fa;
--color-tmd14-surface2: oklch(0.973 0.006 264.5);
--color-tmd14-primary: #1565c0;
--color-tmd14-primary: oklch(0.513 0.16 255.7);
--color-tmd14-secondarycontainer: #dbe7f7;
--color-tmd14-secondarycontainer: oklch(0.924 0.025 255.6);
--color-tmd14-onsecondarycontainer: #0a2a4a;
--color-tmd14-onsecondarycontainer: oklch(0.281 0.07 251.8);
--color-tmd14-onsurface: #1c1b1f;
--color-tmd14-onsurface: oklch(0.225 0.008 297.2);
--color-tmd14-onvariant: #4d5966;
--color-tmd14-onvariant: oklch(0.459 0.026 250.5);
--color-tmd14-outlinevariant: #dde1e7;
--color-tmd14-outlinevariant: oklch(0.908 0.009 258.3);
--color-tmd14-error: #b3261e;
--color-tmd14-error: oklch(0.501 0.178 28.7);
--font-tmd14-sans: 'Roboto Flex', 'Roboto', system-ui, sans-serif;
--font-tmd14-mono: 'Roboto Mono', ui-monospace, monospace;
--font-tmd14-icon: 'Material Symbols Outlined';
--shadow-tmd14-1: 0 1px 2px 0 rgb(0 0 0 / 0.3), 0 1px 3px 1px rgb(0 0 0 / 0.15);
--shadow-tmd14-2: 0 1px 2px 0 rgb(0 0 0 / 0.3), 0 2px 6px 2px rgb(0 0 0 / 0.15);
}
.tmd-14 {
width: 100%;
min-height: 100svh;
}
.tmd-14 .font-tmd14-icon {
font-feature-settings: 'liga';
}(function () {
document.querySelectorAll('.tmd-14').forEach(function (root) {
if (root.dataset.bound) return; root.dataset.bound = '1';
var btn = root.querySelector('[data-drawer-open]'),
closeBtn = root.querySelector('[data-drawer-close]'),
drawer = root.querySelector('#tmd14-drawer'),
scrim = root.querySelector('[data-scrim]'),
main = root.querySelector('#tmd14-main'),
bar = root.querySelector('[data-bar]'),
scroller = root.closest('[data-scroll-root]') || document.scrollingElement;
function set(open) {
drawer.toggleAttribute('data-open', open);
scrim.toggleAttribute('data-open', open);
btn.setAttribute('aria-expanded', String(open));
if (open) { main.setAttribute('inert', ''); drawer.querySelector('a').focus(); }
else { main.removeAttribute('inert'); btn.focus(); }
}
btn.addEventListener('click', function () { set(true); });
closeBtn.addEventListener('click', function () { set(false); });
scrim.addEventListener('click', function () { set(false); });
root.addEventListener('keydown', function (e) {
if (e.key === 'Escape' && drawer.hasAttribute('data-open')) set(false);
});
function onScroll() {
var y = scroller === document.scrollingElement ? window.scrollY : scroller.scrollTop;
bar.toggleAttribute('data-scrolled', y > 4);
}
(scroller === document.scrollingElement ? window : scroller).addEventListener('scroll', onScroll, { passive: true });
onScroll();
});
})();(function () {
document.querySelectorAll('.tmd-14').forEach(function (root) {
if (root.dataset.bound) return; root.dataset.bound = '1';
var btn = root.querySelector('[data-drawer-open]'),
closeBtn = root.querySelector('[data-drawer-close]'),
drawer = root.querySelector('#tmd14-drawer'),
scrim = root.querySelector('[data-scrim]'),
main = root.querySelector('#tmd14-main'),
bar = root.querySelector('[data-bar]'),
scroller = root.closest('[data-scroll-root]') || document.scrollingElement;
function set(open) {
drawer.toggleAttribute('data-open', open);
scrim.toggleAttribute('data-open', open);
btn.setAttribute('aria-expanded', String(open));
if (open) { main.setAttribute('inert', ''); drawer.querySelector('a').focus(); }
else { main.removeAttribute('inert'); btn.focus(); }
}
btn.addEventListener('click', function () { set(true); });
closeBtn.addEventListener('click', function () { set(false); });
scrim.addEventListener('click', function () { set(false); });
root.addEventListener('keydown', function (e) {
if (e.key === 'Escape' && drawer.hasAttribute('data-open')) set(false);
});
function onScroll() {
var y = scroller === document.scrollingElement ? window.scrollY : scroller.scrollTop;
bar.toggleAttribute('data-scrolled', y > 4);
}
(scroller === document.scrollingElement ? window : scroller).addEventListener('scroll', onScroll, { passive: true });
onScroll();
});
})();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-tmd14-bg: #eef1f6;
--color-tmd14-bg: oklch(0.957 0.007 260.7);
--color-tmd14-surface: #ffffff;
--color-tmd14-surface: oklch(1 0 89.9);
--color-tmd14-surface2: #f4f6fa;
--color-tmd14-surface2: oklch(0.973 0.006 264.5);
--color-tmd14-primary: #1565c0;
--color-tmd14-primary: oklch(0.513 0.16 255.7);
--color-tmd14-secondarycontainer: #dbe7f7;
--color-tmd14-secondarycontainer: oklch(0.924 0.025 255.6);
--color-tmd14-onsecondarycontainer: #0a2a4a;
--color-tmd14-onsecondarycontainer: oklch(0.281 0.07 251.8);
--color-tmd14-onsurface: #1c1b1f;
--color-tmd14-onsurface: oklch(0.225 0.008 297.2);
--color-tmd14-onvariant: #4d5966;
--color-tmd14-onvariant: oklch(0.459 0.026 250.5);
--color-tmd14-outlinevariant: #dde1e7;
--color-tmd14-outlinevariant: oklch(0.908 0.009 258.3);
--color-tmd14-error: #b3261e;
--color-tmd14-error: oklch(0.501 0.178 28.7);
--font-tmd14-sans: 'Roboto Flex', 'Roboto', system-ui, sans-serif;
--font-tmd14-mono: 'Roboto Mono', ui-monospace, monospace;
--font-tmd14-icon: 'Material Symbols Outlined';
--shadow-tmd14-1: 0 1px 2px 0 rgb(0 0 0 / 0.3), 0 1px 3px 1px rgb(0 0 0 / 0.15);
--shadow-tmd14-2: 0 1px 2px 0 rgb(0 0 0 / 0.3), 0 2px 6px 2px rgb(0 0 0 / 0.15);
}
.tmd-14 {
width: 100%;
min-height: 100svh;
}
.tmd-14 .font-tmd14-icon {
font-feature-settings: 'liga';
}@import "tailwindcss";
@theme {
--color-tmd14-bg: #eef1f6;
--color-tmd14-bg: oklch(0.957 0.007 260.7);
--color-tmd14-surface: #ffffff;
--color-tmd14-surface: oklch(1 0 89.9);
--color-tmd14-surface2: #f4f6fa;
--color-tmd14-surface2: oklch(0.973 0.006 264.5);
--color-tmd14-primary: #1565c0;
--color-tmd14-primary: oklch(0.513 0.16 255.7);
--color-tmd14-secondarycontainer: #dbe7f7;
--color-tmd14-secondarycontainer: oklch(0.924 0.025 255.6);
--color-tmd14-onsecondarycontainer: #0a2a4a;
--color-tmd14-onsecondarycontainer: oklch(0.281 0.07 251.8);
--color-tmd14-onsurface: #1c1b1f;
--color-tmd14-onsurface: oklch(0.225 0.008 297.2);
--color-tmd14-onvariant: #4d5966;
--color-tmd14-onvariant: oklch(0.459 0.026 250.5);
--color-tmd14-outlinevariant: #dde1e7;
--color-tmd14-outlinevariant: oklch(0.908 0.009 258.3);
--color-tmd14-error: #b3261e;
--color-tmd14-error: oklch(0.501 0.178 28.7);
--font-tmd14-sans: 'Roboto Flex', 'Roboto', system-ui, sans-serif;
--font-tmd14-mono: 'Roboto Mono', ui-monospace, monospace;
--font-tmd14-icon: 'Material Symbols Outlined';
--shadow-tmd14-1: 0 1px 2px 0 rgb(0 0 0 / 0.3), 0 1px 3px 1px rgb(0 0 0 / 0.15);
--shadow-tmd14-2: 0 1px 2px 0 rgb(0 0 0 / 0.3), 0 2px 6px 2px rgb(0 0 0 / 0.15);
}
.tmd-14 {
width: 100%;
min-height: 100svh;
}
.tmd-14 .font-tmd14-icon {
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.
- The CSS version's
--nav-width/--topbar-hvariables and media-query blocks become two responsivegrid-cols-[…]modifiers. The layout is legible in one attribute. - The rail and the drawer are the same list of destinations at two densities, achieved with
lg:modifiers on the item rather than two components — which also keeps one set of ids. - Scroll elevation is an attribute, not a class: the listener sets
data-scrolledanddata-scrolled:shadow-tmd14-2decides what that looks like. Restyling never means reopening the script. inerton the main content is the drawer's most important line and the one most ports omit; without it the tab order walks behind the scrim.- The scrim is a real
<button>with an accessible label, so the drawer is dismissible by keyboard as well as by pointer. - This demo deliberately reuses the state-layer, list, badge and progress recipes from earlier demos — assembled, they are the argument that the collection is one system rather than fourteen tricks.
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 Responsive App Shell Layout
Source: https://codefronts.com/design-styles/tailwind-material-design-components/tailwind-material-design-responsive-app-shell-layout/
The whole thing assembled: a sticky top app bar that gains elevation on scroll, a navigation rail that becomes a modal drawer under md, and a dashboard grid of the earlier demos' components. Every M3 layout breakpoint is a Tailwind responsive modifier, and the only JavaScript is the drawer's focus management and one scroll listener.
## HTML
```html
<section class="tmd-14 w-full min-h-svh bg-tmd14-bg font-tmd14-sans text-tmd14-onsurface" aria-label="Material Design app shell demo">
<div class="relative grid md:grid-cols-[80px_1fr] lg:grid-cols-[256px_1fr] min-h-svh">
<nav aria-label="Main" class="hidden md:grid content-start gap-1 py-3 px-2 bg-tmd14-surface2 border-r border-tmd14-outlinevariant">
<span class="hidden lg:block px-4 py-3 font-tmd14-mono text-[11px] uppercase tracking-[0.12em] text-tmd14-onvariant">Hydrolab</span>
<a href="#dash" aria-current="page" class="relative overflow-hidden grid lg:grid-cols-[auto_1fr] justify-items-center lg:justify-items-start items-center gap-1 lg:gap-3 min-h-14 px-2 lg:px-4 rounded-full no-underline text-[11px] lg:text-sm font-medium text-tmd14-onvariant transition-colors duration-200 aria-[current=page]:bg-tmd14-secondarycontainer aria-[current=page]:text-tmd14-onsecondarycontainer focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tmd14-primary before:absolute before:inset-0 before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08]"><span class="font-tmd14-icon text-[22px] leading-none" aria-hidden="true">dashboard</span>Overview</a>
<a href="#stations" class="relative overflow-hidden grid lg:grid-cols-[auto_1fr] justify-items-center lg:justify-items-start items-center gap-1 lg:gap-3 min-h-14 px-2 lg:px-4 rounded-full no-underline text-[11px] lg:text-sm font-medium text-tmd14-onvariant transition-colors duration-200 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tmd14-primary before:absolute before:inset-0 before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08]"><span class="font-tmd14-icon text-[22px] leading-none" aria-hidden="true">sensors</span>Stations</a>
<a href="#reports" class="relative overflow-hidden grid lg:grid-cols-[auto_1fr] justify-items-center lg:justify-items-start items-center gap-1 lg:gap-3 min-h-14 px-2 lg:px-4 rounded-full no-underline text-[11px] lg:text-sm font-medium text-tmd14-onvariant transition-colors duration-200 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tmd14-primary before:absolute before:inset-0 before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08]"><span class="font-tmd14-icon text-[22px] leading-none" aria-hidden="true">bar_chart</span>Reports</a>
<a href="#team" class="relative overflow-hidden grid lg:grid-cols-[auto_1fr] justify-items-center lg:justify-items-start items-center gap-1 lg:gap-3 min-h-14 px-2 lg:px-4 rounded-full no-underline text-[11px] lg:text-sm font-medium text-tmd14-onvariant transition-colors duration-200 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tmd14-primary before:absolute before:inset-0 before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08]"><span class="font-tmd14-icon text-[22px] leading-none" aria-hidden="true">group</span>Team</a>
</nav>
<div id="tmd14-main" class="grid grid-rows-[auto_1fr] min-w-0">
<header data-bar class="sticky top-0 z-20 flex items-center gap-2 h-16 px-3 bg-tmd14-surface/85 backdrop-blur-sm border-b border-transparent transition-[box-shadow,border-color] duration-200 data-scrolled:shadow-tmd14-2 data-scrolled:border-tmd14-outlinevariant motion-reduce:transition-none">
<button type="button" data-drawer-open aria-expanded="false" aria-controls="tmd14-drawer" aria-label="Open navigation" class="md:hidden relative overflow-hidden grid place-items-center min-w-11 min-h-11 rounded-full border-0 bg-transparent cursor-pointer text-tmd14-onsurface focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tmd14-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-tmd14-icon text-[22px] leading-none" aria-hidden="true">menu</span></button>
<h2 class="text-[17px] font-medium">Overview</h2>
<div class="ml-auto flex items-center gap-1">
<button type="button" aria-label="Search" class="relative overflow-hidden grid place-items-center min-w-11 min-h-11 rounded-full border-0 bg-transparent cursor-pointer text-tmd14-onvariant focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tmd14-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-tmd14-icon text-[22px] leading-none" aria-hidden="true">search</span></button>
<span class="relative inline-block"><button type="button" aria-label="Alerts, 3 unread" class="relative overflow-hidden grid place-items-center min-w-11 min-h-11 rounded-full border-0 bg-transparent cursor-pointer text-tmd14-onvariant focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tmd14-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-tmd14-icon text-[22px] leading-none" aria-hidden="true">notifications</span></button><span class="absolute top-1 right-1 min-w-4 h-4 px-1 grid place-items-center rounded-full bg-tmd14-error text-white font-tmd14-mono text-[10px] pointer-events-none" aria-hidden="true">3</span></span>
<span class="grid place-items-center w-9 h-9 ml-1 rounded-full bg-tmd14-primary text-white font-tmd14-mono text-xs" aria-hidden="true">MK</span>
</div>
</header>
<main class="grid content-start gap-4 p-4 md:p-6">
<div class="grid gap-4 grid-cols-[repeat(auto-fit,minmax(min(240px,100%),1fr))]">
<article class="grid gap-1 p-5 rounded-3xl bg-tmd14-surface shadow-tmd14-1"><span class="font-tmd14-mono text-[11px] uppercase tracking-[0.08em] text-tmd14-onvariant">Stations online</span><b class="text-[32px] font-medium leading-tight tabular-nums">16<span class="text-base text-tmd14-onvariant"> / 18</span></b><div class="h-1 mt-2 rounded-full bg-tmd14-outlinevariant overflow-hidden"><div class="h-full w-[89%] rounded-full bg-tmd14-primary"></div></div></article>
<article class="grid gap-1 p-5 rounded-3xl bg-tmd14-surface shadow-tmd14-1"><span class="font-tmd14-mono text-[11px] uppercase tracking-[0.08em] text-tmd14-onvariant">Readings today</span><b class="text-[32px] font-medium leading-tight tabular-nums">4 210</b><span class="text-xs text-tmd14-onvariant">+6.2% on yesterday</span></article>
<article class="grid gap-1 p-5 rounded-3xl bg-tmd14-secondarycontainer text-tmd14-onsecondarycontainer"><span class="font-tmd14-mono text-[11px] uppercase tracking-[0.08em] opacity-80">Open alerts</span><b class="text-[32px] font-medium leading-tight tabular-nums">3</b><span class="text-xs opacity-80">1 offline · 2 drift</span></article>
</div>
<div class="grid gap-4 lg:grid-cols-[1.4fr_1fr]">
<article class="grid content-start gap-3 p-5 rounded-3xl bg-tmd14-surface shadow-tmd14-1">
<div class="flex items-baseline justify-between gap-3"><b class="text-[15px] font-medium">Depth coverage</b><span class="font-tmd14-mono text-[11px] text-tmd14-onvariant">last 7 days</span></div>
<div class="grid grid-cols-7 items-end gap-2 h-40" role="img" aria-label="Bar chart of depth coverage over seven days">
<div class="h-[62%] rounded-t-lg bg-tmd14-primary/85"></div>
<div class="h-[74%] rounded-t-lg bg-tmd14-primary/85"></div>
<div class="h-[48%] rounded-t-lg bg-tmd14-primary/85"></div>
<div class="h-[86%] rounded-t-lg bg-tmd14-primary/85"></div>
<div class="h-[70%] rounded-t-lg bg-tmd14-primary/85"></div>
<div class="h-[92%] rounded-t-lg bg-tmd14-primary"></div>
<div class="h-[58%] rounded-t-lg bg-tmd14-primary/40"></div>
</div>
</article>
<article class="grid content-start gap-2 p-5 rounded-3xl bg-tmd14-surface shadow-tmd14-1">
<b class="text-[15px] font-medium">Recent activity</b>
<ul role="list" class="list-none m-0 p-0 divide-y divide-tmd14-outlinevariant">
<li class="grid grid-cols-[auto_1fr_auto] items-center gap-3 min-h-14"><span class="grid place-items-center w-9 h-9 rounded-full bg-tmd14-surface2 font-tmd14-icon text-[18px] leading-none text-tmd14-onvariant" aria-hidden="true">warning</span><span class="min-w-0 text-sm truncate">ST-047 went offline</span><small class="font-tmd14-mono text-[11px] text-tmd14-onvariant">12m</small></li>
<li class="grid grid-cols-[auto_1fr_auto] items-center gap-3 min-h-14"><span class="grid place-items-center w-9 h-9 rounded-full bg-tmd14-surface2 font-tmd14-icon text-[18px] leading-none text-tmd14-onvariant" aria-hidden="true">check_circle</span><span class="min-w-0 text-sm truncate">ST-031 recalibrated</span><small class="font-tmd14-mono text-[11px] text-tmd14-onvariant">1h</small></li>
<li class="grid grid-cols-[auto_1fr_auto] items-center gap-3 min-h-14"><span class="grid place-items-center w-9 h-9 rounded-full bg-tmd14-surface2 font-tmd14-icon text-[18px] leading-none text-tmd14-onvariant" aria-hidden="true">cloud_done</span><span class="min-w-0 text-sm truncate">Nightly sync complete</span><small class="font-tmd14-mono text-[11px] text-tmd14-onvariant">6h</small></li>
</ul>
</article>
</div>
<p class="text-xs text-tmd14-onvariant">Resize the frame: single column below <span class="font-tmd14-mono">md</span>, 80px rail at <span class="font-tmd14-mono">md</span>, 256px drawer at <span class="font-tmd14-mono">lg</span>. Scroll to elevate the bar.</p>
<div class="h-40" aria-hidden="true"></div>
</main>
</div>
<button type="button" data-scrim tabindex="-1" aria-label="Close navigation" class="md:hidden fixed inset-0 z-30 hidden border-0 bg-black/40 cursor-pointer data-open:block"></button>
<nav id="tmd14-drawer" aria-label="Navigation drawer" class="md:hidden fixed inset-y-0 left-0 z-40 grid content-start gap-1 w-70 py-3 px-2 bg-tmd14-surface2 [transform:translateX(-100%)] transition-transform duration-300 ease-out data-open:[transform:translateX(0)] motion-reduce:transition-none">
<div class="flex items-center gap-2 px-2 py-2">
<button type="button" data-drawer-close aria-label="Close navigation" class="relative overflow-hidden grid place-items-center min-w-11 min-h-11 rounded-full border-0 bg-transparent cursor-pointer text-tmd14-onsurface focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tmd14-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-tmd14-icon text-[22px] leading-none" aria-hidden="true">close</span></button>
<span class="font-tmd14-mono text-[11px] uppercase tracking-[0.12em] text-tmd14-onvariant">Hydrolab</span>
</div>
<a href="#dash" aria-current="page" class="relative overflow-hidden flex items-center gap-3 min-h-14 px-4 rounded-full no-underline text-sm font-medium text-tmd14-onvariant aria-[current=page]:bg-tmd14-secondarycontainer aria-[current=page]:text-tmd14-onsecondarycontainer focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tmd14-primary before:absolute before:inset-0 before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08]"><span class="font-tmd14-icon text-[22px] leading-none" aria-hidden="true">dashboard</span>Overview</a>
<a href="#stations" class="relative overflow-hidden flex items-center gap-3 min-h-14 px-4 rounded-full no-underline text-sm font-medium text-tmd14-onvariant focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tmd14-primary before:absolute before:inset-0 before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08]"><span class="font-tmd14-icon text-[22px] leading-none" aria-hidden="true">sensors</span>Stations</a>
<a href="#reports" class="relative overflow-hidden flex items-center gap-3 min-h-14 px-4 rounded-full no-underline text-sm font-medium text-tmd14-onvariant focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tmd14-primary before:absolute before:inset-0 before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08]"><span class="font-tmd14-icon text-[22px] leading-none" aria-hidden="true">bar_chart</span>Reports</a>
<a href="#team" class="relative overflow-hidden flex items-center gap-3 min-h-14 px-4 rounded-full no-underline text-sm font-medium text-tmd14-onvariant focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tmd14-primary before:absolute before:inset-0 before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08]"><span class="font-tmd14-icon text-[22px] leading-none" aria-hidden="true">group</span>Team</a>
</nav>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tmd14-bg: #eef1f6;
--color-tmd14-bg: oklch(0.957 0.007 260.7);
--color-tmd14-surface: #ffffff;
--color-tmd14-surface: oklch(1 0 89.9);
--color-tmd14-surface2: #f4f6fa;
--color-tmd14-surface2: oklch(0.973 0.006 264.5);
--color-tmd14-primary: #1565c0;
--color-tmd14-primary: oklch(0.513 0.16 255.7);
--color-tmd14-secondarycontainer: #dbe7f7;
--color-tmd14-secondarycontainer: oklch(0.924 0.025 255.6);
--color-tmd14-onsecondarycontainer: #0a2a4a;
--color-tmd14-onsecondarycontainer: oklch(0.281 0.07 251.8);
--color-tmd14-onsurface: #1c1b1f;
--color-tmd14-onsurface: oklch(0.225 0.008 297.2);
--color-tmd14-onvariant: #4d5966;
--color-tmd14-onvariant: oklch(0.459 0.026 250.5);
--color-tmd14-outlinevariant: #dde1e7;
--color-tmd14-outlinevariant: oklch(0.908 0.009 258.3);
--color-tmd14-error: #b3261e;
--color-tmd14-error: oklch(0.501 0.178 28.7);
--font-tmd14-sans: 'Roboto Flex', 'Roboto', system-ui, sans-serif;
--font-tmd14-mono: 'Roboto Mono', ui-monospace, monospace;
--font-tmd14-icon: 'Material Symbols Outlined';
--shadow-tmd14-1: 0 1px 2px 0 rgb(0 0 0 / 0.3), 0 1px 3px 1px rgb(0 0 0 / 0.15);
--shadow-tmd14-2: 0 1px 2px 0 rgb(0 0 0 / 0.3), 0 2px 6px 2px rgb(0 0 0 / 0.15);
}
.tmd-14 {
width: 100%;
min-height: 100svh;
}
.tmd-14 .font-tmd14-icon {
font-feature-settings: 'liga';
}
```
## JavaScript
```js
(function () {
document.querySelectorAll('.tmd-14').forEach(function (root) {
if (root.dataset.bound) return; root.dataset.bound = '1';
var btn = root.querySelector('[data-drawer-open]'),
closeBtn = root.querySelector('[data-drawer-close]'),
drawer = root.querySelector('#tmd14-drawer'),
scrim = root.querySelector('[data-scrim]'),
main = root.querySelector('#tmd14-main'),
bar = root.querySelector('[data-bar]'),
scroller = root.closest('[data-scroll-root]') || document.scrollingElement;
function set(open) {
drawer.toggleAttribute('data-open', open);
scrim.toggleAttribute('data-open', open);
btn.setAttribute('aria-expanded', String(open));
if (open) { main.setAttribute('inert', ''); drawer.querySelector('a').focus(); }
else { main.removeAttribute('inert'); btn.focus(); }
}
btn.addEventListener('click', function () { set(true); });
closeBtn.addEventListener('click', function () { set(false); });
scrim.addEventListener('click', function () { set(false); });
root.addEventListener('keydown', function (e) {
if (e.key === 'Escape' && drawer.hasAttribute('data-open')) set(false);
});
function onScroll() {
var y = scroller === document.scrollingElement ? window.scrollY : scroller.scrollTop;
bar.toggleAttribute('data-scrolled', y > 4);
}
(scroller === document.scrollingElement ? window : scroller).addEventListener('scroll', onScroll, { passive: true });
onScroll();
});
})();
```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 Responsive App Shell Layout
Source: https://codefronts.com/design-styles/tailwind-material-design-components/tailwind-material-design-responsive-app-shell-layout/
The whole thing assembled: a sticky top app bar that gains elevation on scroll, a navigation rail that becomes a modal drawer under md, and a dashboard grid of the earlier demos' components. Every M3 layout breakpoint is a Tailwind responsive modifier, and the only JavaScript is the drawer's focus management and one scroll listener.
## HTML
```html
<section class="tmd-14 w-full min-h-svh bg-tmd14-bg font-tmd14-sans text-tmd14-onsurface" aria-label="Material Design app shell demo">
<div class="relative grid md:grid-cols-[80px_1fr] lg:grid-cols-[256px_1fr] min-h-svh">
<nav aria-label="Main" class="hidden md:grid content-start gap-1 py-3 px-2 bg-tmd14-surface2 border-r border-tmd14-outlinevariant">
<span class="hidden lg:block px-4 py-3 font-tmd14-mono text-[11px] uppercase tracking-[0.12em] text-tmd14-onvariant">Hydrolab</span>
<a href="#dash" aria-current="page" class="relative overflow-hidden grid lg:grid-cols-[auto_1fr] justify-items-center lg:justify-items-start items-center gap-1 lg:gap-3 min-h-14 px-2 lg:px-4 rounded-full no-underline text-[11px] lg:text-sm font-medium text-tmd14-onvariant transition-colors duration-200 aria-[current=page]:bg-tmd14-secondarycontainer aria-[current=page]:text-tmd14-onsecondarycontainer focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tmd14-primary before:absolute before:inset-0 before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08]"><span class="font-tmd14-icon text-[22px] leading-none" aria-hidden="true">dashboard</span>Overview</a>
<a href="#stations" class="relative overflow-hidden grid lg:grid-cols-[auto_1fr] justify-items-center lg:justify-items-start items-center gap-1 lg:gap-3 min-h-14 px-2 lg:px-4 rounded-full no-underline text-[11px] lg:text-sm font-medium text-tmd14-onvariant transition-colors duration-200 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tmd14-primary before:absolute before:inset-0 before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08]"><span class="font-tmd14-icon text-[22px] leading-none" aria-hidden="true">sensors</span>Stations</a>
<a href="#reports" class="relative overflow-hidden grid lg:grid-cols-[auto_1fr] justify-items-center lg:justify-items-start items-center gap-1 lg:gap-3 min-h-14 px-2 lg:px-4 rounded-full no-underline text-[11px] lg:text-sm font-medium text-tmd14-onvariant transition-colors duration-200 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tmd14-primary before:absolute before:inset-0 before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08]"><span class="font-tmd14-icon text-[22px] leading-none" aria-hidden="true">bar_chart</span>Reports</a>
<a href="#team" class="relative overflow-hidden grid lg:grid-cols-[auto_1fr] justify-items-center lg:justify-items-start items-center gap-1 lg:gap-3 min-h-14 px-2 lg:px-4 rounded-full no-underline text-[11px] lg:text-sm font-medium text-tmd14-onvariant transition-colors duration-200 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tmd14-primary before:absolute before:inset-0 before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08]"><span class="font-tmd14-icon text-[22px] leading-none" aria-hidden="true">group</span>Team</a>
</nav>
<div id="tmd14-main" class="grid grid-rows-[auto_1fr] min-w-0">
<header data-bar class="sticky top-0 z-20 flex items-center gap-2 h-16 px-3 bg-tmd14-surface/85 backdrop-blur-sm border-b border-transparent transition-[box-shadow,border-color] duration-200 data-scrolled:shadow-tmd14-2 data-scrolled:border-tmd14-outlinevariant motion-reduce:transition-none">
<button type="button" data-drawer-open aria-expanded="false" aria-controls="tmd14-drawer" aria-label="Open navigation" class="md:hidden relative overflow-hidden grid place-items-center min-w-11 min-h-11 rounded-full border-0 bg-transparent cursor-pointer text-tmd14-onsurface focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tmd14-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-tmd14-icon text-[22px] leading-none" aria-hidden="true">menu</span></button>
<h2 class="text-[17px] font-medium">Overview</h2>
<div class="ml-auto flex items-center gap-1">
<button type="button" aria-label="Search" class="relative overflow-hidden grid place-items-center min-w-11 min-h-11 rounded-full border-0 bg-transparent cursor-pointer text-tmd14-onvariant focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tmd14-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-tmd14-icon text-[22px] leading-none" aria-hidden="true">search</span></button>
<span class="relative inline-block"><button type="button" aria-label="Alerts, 3 unread" class="relative overflow-hidden grid place-items-center min-w-11 min-h-11 rounded-full border-0 bg-transparent cursor-pointer text-tmd14-onvariant focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tmd14-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-tmd14-icon text-[22px] leading-none" aria-hidden="true">notifications</span></button><span class="absolute top-1 right-1 min-w-4 h-4 px-1 grid place-items-center rounded-full bg-tmd14-error text-white font-tmd14-mono text-[10px] pointer-events-none" aria-hidden="true">3</span></span>
<span class="grid place-items-center w-9 h-9 ml-1 rounded-full bg-tmd14-primary text-white font-tmd14-mono text-xs" aria-hidden="true">MK</span>
</div>
</header>
<main class="grid content-start gap-4 p-4 md:p-6">
<div class="grid gap-4 grid-cols-[repeat(auto-fit,minmax(min(240px,100%),1fr))]">
<article class="grid gap-1 p-5 rounded-3xl bg-tmd14-surface shadow-tmd14-1"><span class="font-tmd14-mono text-[11px] uppercase tracking-[0.08em] text-tmd14-onvariant">Stations online</span><b class="text-[32px] font-medium leading-tight tabular-nums">16<span class="text-base text-tmd14-onvariant"> / 18</span></b><div class="h-1 mt-2 rounded-full bg-tmd14-outlinevariant overflow-hidden"><div class="h-full w-[89%] rounded-full bg-tmd14-primary"></div></div></article>
<article class="grid gap-1 p-5 rounded-3xl bg-tmd14-surface shadow-tmd14-1"><span class="font-tmd14-mono text-[11px] uppercase tracking-[0.08em] text-tmd14-onvariant">Readings today</span><b class="text-[32px] font-medium leading-tight tabular-nums">4 210</b><span class="text-xs text-tmd14-onvariant">+6.2% on yesterday</span></article>
<article class="grid gap-1 p-5 rounded-3xl bg-tmd14-secondarycontainer text-tmd14-onsecondarycontainer"><span class="font-tmd14-mono text-[11px] uppercase tracking-[0.08em] opacity-80">Open alerts</span><b class="text-[32px] font-medium leading-tight tabular-nums">3</b><span class="text-xs opacity-80">1 offline · 2 drift</span></article>
</div>
<div class="grid gap-4 lg:grid-cols-[1.4fr_1fr]">
<article class="grid content-start gap-3 p-5 rounded-3xl bg-tmd14-surface shadow-tmd14-1">
<div class="flex items-baseline justify-between gap-3"><b class="text-[15px] font-medium">Depth coverage</b><span class="font-tmd14-mono text-[11px] text-tmd14-onvariant">last 7 days</span></div>
<div class="grid grid-cols-7 items-end gap-2 h-40" role="img" aria-label="Bar chart of depth coverage over seven days">
<div class="h-[62%] rounded-t-lg bg-tmd14-primary/85"></div>
<div class="h-[74%] rounded-t-lg bg-tmd14-primary/85"></div>
<div class="h-[48%] rounded-t-lg bg-tmd14-primary/85"></div>
<div class="h-[86%] rounded-t-lg bg-tmd14-primary/85"></div>
<div class="h-[70%] rounded-t-lg bg-tmd14-primary/85"></div>
<div class="h-[92%] rounded-t-lg bg-tmd14-primary"></div>
<div class="h-[58%] rounded-t-lg bg-tmd14-primary/40"></div>
</div>
</article>
<article class="grid content-start gap-2 p-5 rounded-3xl bg-tmd14-surface shadow-tmd14-1">
<b class="text-[15px] font-medium">Recent activity</b>
<ul role="list" class="list-none m-0 p-0 divide-y divide-tmd14-outlinevariant">
<li class="grid grid-cols-[auto_1fr_auto] items-center gap-3 min-h-14"><span class="grid place-items-center w-9 h-9 rounded-full bg-tmd14-surface2 font-tmd14-icon text-[18px] leading-none text-tmd14-onvariant" aria-hidden="true">warning</span><span class="min-w-0 text-sm truncate">ST-047 went offline</span><small class="font-tmd14-mono text-[11px] text-tmd14-onvariant">12m</small></li>
<li class="grid grid-cols-[auto_1fr_auto] items-center gap-3 min-h-14"><span class="grid place-items-center w-9 h-9 rounded-full bg-tmd14-surface2 font-tmd14-icon text-[18px] leading-none text-tmd14-onvariant" aria-hidden="true">check_circle</span><span class="min-w-0 text-sm truncate">ST-031 recalibrated</span><small class="font-tmd14-mono text-[11px] text-tmd14-onvariant">1h</small></li>
<li class="grid grid-cols-[auto_1fr_auto] items-center gap-3 min-h-14"><span class="grid place-items-center w-9 h-9 rounded-full bg-tmd14-surface2 font-tmd14-icon text-[18px] leading-none text-tmd14-onvariant" aria-hidden="true">cloud_done</span><span class="min-w-0 text-sm truncate">Nightly sync complete</span><small class="font-tmd14-mono text-[11px] text-tmd14-onvariant">6h</small></li>
</ul>
</article>
</div>
<p class="text-xs text-tmd14-onvariant">Resize the frame: single column below <span class="font-tmd14-mono">md</span>, 80px rail at <span class="font-tmd14-mono">md</span>, 256px drawer at <span class="font-tmd14-mono">lg</span>. Scroll to elevate the bar.</p>
<div class="h-40" aria-hidden="true"></div>
</main>
</div>
<button type="button" data-scrim tabindex="-1" aria-label="Close navigation" class="md:hidden fixed inset-0 z-30 hidden border-0 bg-black/40 cursor-pointer data-open:block"></button>
<nav id="tmd14-drawer" aria-label="Navigation drawer" class="md:hidden fixed inset-y-0 left-0 z-40 grid content-start gap-1 w-70 py-3 px-2 bg-tmd14-surface2 [transform:translateX(-100%)] transition-transform duration-300 ease-out data-open:[transform:translateX(0)] motion-reduce:transition-none">
<div class="flex items-center gap-2 px-2 py-2">
<button type="button" data-drawer-close aria-label="Close navigation" class="relative overflow-hidden grid place-items-center min-w-11 min-h-11 rounded-full border-0 bg-transparent cursor-pointer text-tmd14-onsurface focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tmd14-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-tmd14-icon text-[22px] leading-none" aria-hidden="true">close</span></button>
<span class="font-tmd14-mono text-[11px] uppercase tracking-[0.12em] text-tmd14-onvariant">Hydrolab</span>
</div>
<a href="#dash" aria-current="page" class="relative overflow-hidden flex items-center gap-3 min-h-14 px-4 rounded-full no-underline text-sm font-medium text-tmd14-onvariant aria-[current=page]:bg-tmd14-secondarycontainer aria-[current=page]:text-tmd14-onsecondarycontainer focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tmd14-primary before:absolute before:inset-0 before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08]"><span class="font-tmd14-icon text-[22px] leading-none" aria-hidden="true">dashboard</span>Overview</a>
<a href="#stations" class="relative overflow-hidden flex items-center gap-3 min-h-14 px-4 rounded-full no-underline text-sm font-medium text-tmd14-onvariant focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tmd14-primary before:absolute before:inset-0 before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08]"><span class="font-tmd14-icon text-[22px] leading-none" aria-hidden="true">sensors</span>Stations</a>
<a href="#reports" class="relative overflow-hidden flex items-center gap-3 min-h-14 px-4 rounded-full no-underline text-sm font-medium text-tmd14-onvariant focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tmd14-primary before:absolute before:inset-0 before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08]"><span class="font-tmd14-icon text-[22px] leading-none" aria-hidden="true">bar_chart</span>Reports</a>
<a href="#team" class="relative overflow-hidden flex items-center gap-3 min-h-14 px-4 rounded-full no-underline text-sm font-medium text-tmd14-onvariant focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tmd14-primary before:absolute before:inset-0 before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08]"><span class="font-tmd14-icon text-[22px] leading-none" aria-hidden="true">group</span>Team</a>
</nav>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tmd14-bg: #eef1f6;
--color-tmd14-bg: oklch(0.957 0.007 260.7);
--color-tmd14-surface: #ffffff;
--color-tmd14-surface: oklch(1 0 89.9);
--color-tmd14-surface2: #f4f6fa;
--color-tmd14-surface2: oklch(0.973 0.006 264.5);
--color-tmd14-primary: #1565c0;
--color-tmd14-primary: oklch(0.513 0.16 255.7);
--color-tmd14-secondarycontainer: #dbe7f7;
--color-tmd14-secondarycontainer: oklch(0.924 0.025 255.6);
--color-tmd14-onsecondarycontainer: #0a2a4a;
--color-tmd14-onsecondarycontainer: oklch(0.281 0.07 251.8);
--color-tmd14-onsurface: #1c1b1f;
--color-tmd14-onsurface: oklch(0.225 0.008 297.2);
--color-tmd14-onvariant: #4d5966;
--color-tmd14-onvariant: oklch(0.459 0.026 250.5);
--color-tmd14-outlinevariant: #dde1e7;
--color-tmd14-outlinevariant: oklch(0.908 0.009 258.3);
--color-tmd14-error: #b3261e;
--color-tmd14-error: oklch(0.501 0.178 28.7);
--font-tmd14-sans: 'Roboto Flex', 'Roboto', system-ui, sans-serif;
--font-tmd14-mono: 'Roboto Mono', ui-monospace, monospace;
--font-tmd14-icon: 'Material Symbols Outlined';
--shadow-tmd14-1: 0 1px 2px 0 rgb(0 0 0 / 0.3), 0 1px 3px 1px rgb(0 0 0 / 0.15);
--shadow-tmd14-2: 0 1px 2px 0 rgb(0 0 0 / 0.3), 0 2px 6px 2px rgb(0 0 0 / 0.15);
}
.tmd-14 {
width: 100%;
min-height: 100svh;
}
.tmd-14 .font-tmd14-icon {
font-feature-settings: 'liga';
}
```
## JavaScript
```js
(function () {
document.querySelectorAll('.tmd-14').forEach(function (root) {
if (root.dataset.bound) return; root.dataset.bound = '1';
var btn = root.querySelector('[data-drawer-open]'),
closeBtn = root.querySelector('[data-drawer-close]'),
drawer = root.querySelector('#tmd14-drawer'),
scrim = root.querySelector('[data-scrim]'),
main = root.querySelector('#tmd14-main'),
bar = root.querySelector('[data-bar]'),
scroller = root.closest('[data-scroll-root]') || document.scrollingElement;
function set(open) {
drawer.toggleAttribute('data-open', open);
scrim.toggleAttribute('data-open', open);
btn.setAttribute('aria-expanded', String(open));
if (open) { main.setAttribute('inert', ''); drawer.querySelector('a').focus(); }
else { main.removeAttribute('inert'); btn.focus(); }
}
btn.addEventListener('click', function () { set(true); });
closeBtn.addEventListener('click', function () { set(false); });
scrim.addEventListener('click', function () { set(false); });
root.addEventListener('keydown', function (e) {
if (e.key === 'Escape' && drawer.hasAttribute('data-open')) set(false);
});
function onScroll() {
var y = scroller === document.scrollingElement ? window.scrollY : scroller.scrollTop;
bar.toggleAttribute('data-scrolled', y > 4);
}
(scroller === document.scrollingElement ? window : scroller).addEventListener('scroll', onScroll, { passive: true });
onScroll();
});
})();
```How this works
The breakpoints are the layout. M3 specifies a rail from 600px and a standard drawer from 1240px. That is two responsive modifiers:
<div class="grid md:grid-cols-[80px_1fr] lg:grid-cols-[256px_1fr]">Below md the grid is a single column and navigation moves into the modal drawer — which is the same markup, shown with md:hidden. No duplicated nav, no JS measuring.
Scroll elevation, without touching style. One listener sets data-scrolled on the bar; the bar's class list says what that means. If the design later wants a border instead of a shadow, the JS is untouched — which is the whole argument for attribute-driven state.
The drawer is a disclosure with manners. Opening it sets inert on the main content, moves focus to the first link, and Esc returns focus to the button. The scrim is a real focusable button with an accessible label, not a div with a click handler.
Make it yours
- Rail and drawer widths are the two
grid-cols-[…]values; change them and every child reflows. - Top bar height:
h-16on the bar; M3's medium and large top bars areh-28andh-38with the title moving to the second row. - Swap the scroll shadow for a tonal fill: replace
data-scrolled:shadow-tmd14-2withdata-scrolled:bg-tmd14-surface2. - Add a FAB with
fixed bottom-6 right-6 md:hiddenfor the mobile layout only. - Drop the dashboard cards and mount your own content in the
<main>; the shell makes no assumptions about it.
Gotchas — read before shipping
- A modal drawer must make the rest of the page
inert, or a keyboard user tabs behind the scrim into content they cannot see. - The scrim must be a
<button>(or the drawer a<dialog>). A click-handlingdivis unreachable by keyboard and unannounced. stickyfails silently if any ancestor hasoverflow:hidden— the most common app-shell bug, and one screenshots never reveal.- Do not render navigation twice with
md:hiddenon a duplicate; duplicate landmarks and duplicate ids confuse screen readers. Move the one nav, or label them distinctly as here. - A translucent bar needs a fallback background for browsers without
backdrop-filter, which is why the opacity is 85% and not 40%. data-opentoggling must also updatearia-expandedon the trigger; the attribute pair is the contract.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
inert is Baseline 2023 (Chrome 102, Safari 15.5, Firefox 112). backdrop-filter is Baseline; the layout itself needs only CSS grid.