18 / 28
All 28 in 28 Tailwind Hamburger Menus
- 01Tailwind Hamburger Menu with Clip-Path Circle Expansion
- 02Tailwind Keyboard Navigable Hamburger Menu with ARIA Attributes
- 03Tailwind Animated 3-Line to X Hamburger Icon Toggle
- 04Tailwind Slide-In Off-Canvas Drawer with Dimmed Backdrop Overlay
- 05Pure Tailwind Checkbox Hack Hamburger Navigation (No JavaScript)
- 06Tailwind Responsive Hamburger Menu with Integrated Search Bar & Social Links
- 07Tailwind Glassmorphism Backdrop Blur Hamburger Navigation
- 08Tailwind Push Page Navigation Menu (Off-Canvas Content Shift)
- 09Pure Tailwind Hamburger Menu Without JavaScript
- 10Tailwind Hamburger Menu Morphing to Close Icon Animation
- 11Tailwind Full Screen Mobile Navigation Overlay
- 12Tailwind Slide Out Sidebar Navigation / Off Canvas Menu
- 13Tailwind Responsive Navbar Mobile Hamburger Toggle Breakpoints
- 14Tailwind Minimalist Floating Hamburger Button
- 15Tailwind CSS Responsive Hamburger Menu Component
- 16Tailwind Rebuild of the Bootstrap 5 Navbar Hamburger Toggle
- 17Tailwind WCAG Accessible Hamburger Menu Example
- 18Pure Tailwind Dropdown Slide Down Mobile Hamburger Menu
- 19Simple 3-Line Tailwind Hamburger Icon Hover Effect
- 20Tailwind Full Screen Blur Overlay Hamburger Menu
- 21Tailwind Mobile Bottom Navigation Bar with Floating Hamburger Toggle
- 22Pure Tailwind Multi-Level Accordion Hamburger Dropdown Menu
- 23Flexbox Navbar Right-Aligned Tailwind Hamburger Icon
- 24Radial Ripple Expand Tailwind Hamburger Menu
- 25Sticky Tailwind Navbar Shrinking Hamburger Icon on Scroll
- 26Neumorphic Tailwind Hamburger Toggle Button
- 27Brutalist Design Bordered Tailwind Hamburger Menu
- 28Tailwind 3D Flip Canvas Mobile Hamburger Navigation
Pure Tailwind Dropdown Slide Down Mobile Hamburger Menu
How to animate a menu to height:auto — the question with a decade of bad answers — solved twice in utilities: the [grid-template-rows:0fr] → 1fr technique that is Baseline everywhere, and the 2026-native interpolate-size: allow-keywords written out beside it as the alternative. They are alternatives, not layers — the second one swaps the panel's display model, so this demo ships the grid version and documents the other. Zero JavaScript.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section class="thb-18 w-full min-h-svh bg-thb18-bg font-thb18-sans text-thb18-ink px-5 py-9" aria-label="Slide down dropdown hamburger menu demo">
<div class="min-h-100 flex flex-col items-center justify-center gap-4 p-6.5 rounded-[18px] border border-thb18-edge bg-thb18-bg">
<div class="group relative w-[min(420px,100%)] overflow-hidden rounded-2xl border border-thb18-edge bg-thb18-panel shadow-[0_16px_38px_-28px_oklch(0.35_0.06_50/0.55)]">
<input type="checkbox" id="thb18-t" class="peer absolute w-px h-px overflow-hidden whitespace-nowrap [clip-path:inset(50%)]">
<header class="flex items-center justify-between p-3 pl-5">
<span class="flex items-center gap-2.5 text-[15.5px] font-extrabold tracking-[-0.01em]">
<i class="w-5 h-5 rounded-full bg-[radial-gradient(circle_at_35%_30%,oklch(0.8_0.1_70),var(--color-thb18-acc)_70%)]" aria-hidden="true"></i>brewline
</span>
<label for="thb18-t" class="grid place-items-center w-11.5 h-11.5 rounded-xl border border-thb18-edge cursor-pointer select-none transition-[background,border-color] duration-200 hover:bg-thb18-hover peer-focus-visible:outline-2 peer-focus-visible:outline-thb18-acc peer-focus-visible:outline-offset-2">
<span class="relative block w-4.5 h-3" aria-hidden="true">
<i class="absolute left-0 top-0 w-4.5 h-0.5 rounded-sm bg-thb18-ink transition-[translate,rotate] duration-280 group-has-checked:translate-y-[5px] group-has-checked:rotate-45 motion-reduce:transition-none"></i>
<i class="absolute left-0 top-[5px] w-4.5 h-0.5 rounded-sm bg-thb18-ink transition-[opacity] duration-200 group-has-checked:opacity-0 motion-reduce:transition-none"></i>
<i class="absolute left-0 top-2.5 w-4.5 h-0.5 rounded-sm bg-thb18-ink transition-[translate,rotate] duration-280 group-has-checked:-translate-y-[5px] group-has-checked:-rotate-45 motion-reduce:transition-none"></i>
</span>
</label>
</header>
<nav aria-label="Primary" class="thb18-panel grid [grid-template-rows:0fr] transition-[grid-template-rows] duration-400 ease-thb18 group-has-checked:[grid-template-rows:1fr] motion-reduce:transition-none">
<ul class="list-none min-h-0 overflow-hidden">
<li style="--i:0" class="opacity-0 -translate-y-2 transition-[opacity,translate] duration-300 ease-thb18 group-has-checked:opacity-100 group-has-checked:translate-y-0 group-has-checked:[transition-delay:calc(120ms+var(--i)*40ms)] motion-reduce:translate-y-0 motion-reduce:duration-150"><a href="#menu" aria-current="page" class="group/l flex items-center justify-between min-h-12 px-5 border-t border-[color-mix(in_oklch,var(--color-thb18-edge)_55%,transparent)] text-[14.5px] font-semibold text-thb18-mut no-underline transition-[background,color] duration-150 hover:bg-thb18-hover hover:text-thb18-ink focus-visible:bg-thb18-hover focus-visible:text-thb18-ink focus-visible:outline-2 focus-visible:outline-thb18-acc focus-visible:-outline-offset-2 aria-current:text-thb18-acc">Menu<span class="opacity-0 -translate-x-1.5 transition-[opacity,translate] duration-200 group-hover/l:opacity-100 group-hover/l:translate-x-0 group-focus-visible/l:opacity-100 group-focus-visible/l:translate-x-0" aria-hidden="true">→</span></a></li>
<li style="--i:1" class="opacity-0 -translate-y-2 transition-[opacity,translate] duration-300 ease-thb18 group-has-checked:opacity-100 group-has-checked:translate-y-0 group-has-checked:[transition-delay:calc(120ms+var(--i)*40ms)] motion-reduce:translate-y-0 motion-reduce:duration-150"><a href="#locations" class="group/l flex items-center justify-between min-h-12 px-5 border-t border-[color-mix(in_oklch,var(--color-thb18-edge)_55%,transparent)] text-[14.5px] font-semibold text-thb18-mut no-underline transition-[background,color] duration-150 hover:bg-thb18-hover hover:text-thb18-ink focus-visible:bg-thb18-hover focus-visible:text-thb18-ink focus-visible:outline-2 focus-visible:outline-thb18-acc focus-visible:-outline-offset-2">Locations<span class="opacity-0 -translate-x-1.5 transition-[opacity,translate] duration-200 group-hover/l:opacity-100 group-hover/l:translate-x-0 group-focus-visible/l:opacity-100 group-focus-visible/l:translate-x-0" aria-hidden="true">→</span></a></li>
<li style="--i:2" class="opacity-0 -translate-y-2 transition-[opacity,translate] duration-300 ease-thb18 group-has-checked:opacity-100 group-has-checked:translate-y-0 group-has-checked:[transition-delay:calc(120ms+var(--i)*40ms)] motion-reduce:translate-y-0 motion-reduce:duration-150"><a href="#beans" class="group/l flex items-center justify-between min-h-12 px-5 border-t border-[color-mix(in_oklch,var(--color-thb18-edge)_55%,transparent)] text-[14.5px] font-semibold text-thb18-mut no-underline transition-[background,color] duration-150 hover:bg-thb18-hover hover:text-thb18-ink focus-visible:bg-thb18-hover focus-visible:text-thb18-ink focus-visible:outline-2 focus-visible:outline-thb18-acc focus-visible:-outline-offset-2">Beans & gear<span class="opacity-0 -translate-x-1.5 transition-[opacity,translate] duration-200 group-hover/l:opacity-100 group-hover/l:translate-x-0 group-focus-visible/l:opacity-100 group-focus-visible/l:translate-x-0" aria-hidden="true">→</span></a></li>
<li style="--i:3" class="opacity-0 -translate-y-2 transition-[opacity,translate] duration-300 ease-thb18 group-has-checked:opacity-100 group-has-checked:translate-y-0 group-has-checked:[transition-delay:calc(120ms+var(--i)*40ms)] motion-reduce:translate-y-0 motion-reduce:duration-150"><a href="#wholesale" class="group/l flex items-center justify-between min-h-12 px-5 border-t border-[color-mix(in_oklch,var(--color-thb18-edge)_55%,transparent)] text-[14.5px] font-semibold text-thb18-mut no-underline transition-[background,color] duration-150 hover:bg-thb18-hover hover:text-thb18-ink focus-visible:bg-thb18-hover focus-visible:text-thb18-ink focus-visible:outline-2 focus-visible:outline-thb18-acc focus-visible:-outline-offset-2">Wholesale<span class="opacity-0 -translate-x-1.5 transition-[opacity,translate] duration-200 group-hover/l:opacity-100 group-hover/l:translate-x-0 group-focus-visible/l:opacity-100 group-focus-visible/l:translate-x-0" aria-hidden="true">→</span></a></li>
<li style="--i:4" class="opacity-0 -translate-y-2 transition-[opacity,translate] duration-300 ease-thb18 group-has-checked:opacity-100 group-has-checked:translate-y-0 group-has-checked:[transition-delay:calc(120ms+var(--i)*40ms)] motion-reduce:translate-y-0 motion-reduce:duration-150"><a href="#careers" class="group/l flex items-center justify-between min-h-12 px-5 border-t border-[color-mix(in_oklch,var(--color-thb18-edge)_55%,transparent)] text-[14.5px] font-semibold text-thb18-mut no-underline transition-[background,color] duration-150 hover:bg-thb18-hover hover:text-thb18-ink focus-visible:bg-thb18-hover focus-visible:text-thb18-ink focus-visible:outline-2 focus-visible:outline-thb18-acc focus-visible:-outline-offset-2">Careers<span class="opacity-0 -translate-x-1.5 transition-[opacity,translate] duration-200 group-hover/l:opacity-100 group-hover/l:translate-x-0 group-focus-visible/l:opacity-100 group-focus-visible/l:translate-x-0" aria-hidden="true">→</span></a></li>
</ul>
</nav>
</div>
<p class="w-[min(420px,100%)] text-center text-xs leading-relaxed text-thb18-mut text-pretty" aria-hidden="true">opens to <b class="font-semibold text-thb18-ink">height:auto</b> — grid <code class="font-thb18-mono text-[11px] px-1.5 py-0.5 rounded bg-thb18-code">0fr→1fr</code> base, <code class="font-thb18-mono text-[11px] px-1.5 py-0.5 rounded bg-thb18-code">interpolate-size</code> where supported. No max-height hack.</p>
</div>
</section><section class="thb-18 w-full min-h-svh bg-thb18-bg font-thb18-sans text-thb18-ink px-5 py-9" aria-label="Slide down dropdown hamburger menu demo">
<div class="min-h-100 flex flex-col items-center justify-center gap-4 p-6.5 rounded-[18px] border border-thb18-edge bg-thb18-bg">
<div class="group relative w-[min(420px,100%)] overflow-hidden rounded-2xl border border-thb18-edge bg-thb18-panel shadow-[0_16px_38px_-28px_oklch(0.35_0.06_50/0.55)]">
<input type="checkbox" id="thb18-t" class="peer absolute w-px h-px overflow-hidden whitespace-nowrap [clip-path:inset(50%)]">
<header class="flex items-center justify-between p-3 pl-5">
<span class="flex items-center gap-2.5 text-[15.5px] font-extrabold tracking-[-0.01em]">
<i class="w-5 h-5 rounded-full bg-[radial-gradient(circle_at_35%_30%,oklch(0.8_0.1_70),var(--color-thb18-acc)_70%)]" aria-hidden="true"></i>brewline
</span>
<label for="thb18-t" class="grid place-items-center w-11.5 h-11.5 rounded-xl border border-thb18-edge cursor-pointer select-none transition-[background,border-color] duration-200 hover:bg-thb18-hover peer-focus-visible:outline-2 peer-focus-visible:outline-thb18-acc peer-focus-visible:outline-offset-2">
<span class="relative block w-4.5 h-3" aria-hidden="true">
<i class="absolute left-0 top-0 w-4.5 h-0.5 rounded-sm bg-thb18-ink transition-[translate,rotate] duration-280 group-has-checked:translate-y-[5px] group-has-checked:rotate-45 motion-reduce:transition-none"></i>
<i class="absolute left-0 top-[5px] w-4.5 h-0.5 rounded-sm bg-thb18-ink transition-[opacity] duration-200 group-has-checked:opacity-0 motion-reduce:transition-none"></i>
<i class="absolute left-0 top-2.5 w-4.5 h-0.5 rounded-sm bg-thb18-ink transition-[translate,rotate] duration-280 group-has-checked:-translate-y-[5px] group-has-checked:-rotate-45 motion-reduce:transition-none"></i>
</span>
</label>
</header>
<nav aria-label="Primary" class="thb18-panel grid [grid-template-rows:0fr] transition-[grid-template-rows] duration-400 ease-thb18 group-has-checked:[grid-template-rows:1fr] motion-reduce:transition-none">
<ul class="list-none min-h-0 overflow-hidden">
<li style="--i:0" class="opacity-0 -translate-y-2 transition-[opacity,translate] duration-300 ease-thb18 group-has-checked:opacity-100 group-has-checked:translate-y-0 group-has-checked:[transition-delay:calc(120ms+var(--i)*40ms)] motion-reduce:translate-y-0 motion-reduce:duration-150"><a href="#menu" aria-current="page" class="group/l flex items-center justify-between min-h-12 px-5 border-t border-[color-mix(in_oklch,var(--color-thb18-edge)_55%,transparent)] text-[14.5px] font-semibold text-thb18-mut no-underline transition-[background,color] duration-150 hover:bg-thb18-hover hover:text-thb18-ink focus-visible:bg-thb18-hover focus-visible:text-thb18-ink focus-visible:outline-2 focus-visible:outline-thb18-acc focus-visible:-outline-offset-2 aria-current:text-thb18-acc">Menu<span class="opacity-0 -translate-x-1.5 transition-[opacity,translate] duration-200 group-hover/l:opacity-100 group-hover/l:translate-x-0 group-focus-visible/l:opacity-100 group-focus-visible/l:translate-x-0" aria-hidden="true">→</span></a></li>
<li style="--i:1" class="opacity-0 -translate-y-2 transition-[opacity,translate] duration-300 ease-thb18 group-has-checked:opacity-100 group-has-checked:translate-y-0 group-has-checked:[transition-delay:calc(120ms+var(--i)*40ms)] motion-reduce:translate-y-0 motion-reduce:duration-150"><a href="#locations" class="group/l flex items-center justify-between min-h-12 px-5 border-t border-[color-mix(in_oklch,var(--color-thb18-edge)_55%,transparent)] text-[14.5px] font-semibold text-thb18-mut no-underline transition-[background,color] duration-150 hover:bg-thb18-hover hover:text-thb18-ink focus-visible:bg-thb18-hover focus-visible:text-thb18-ink focus-visible:outline-2 focus-visible:outline-thb18-acc focus-visible:-outline-offset-2">Locations<span class="opacity-0 -translate-x-1.5 transition-[opacity,translate] duration-200 group-hover/l:opacity-100 group-hover/l:translate-x-0 group-focus-visible/l:opacity-100 group-focus-visible/l:translate-x-0" aria-hidden="true">→</span></a></li>
<li style="--i:2" class="opacity-0 -translate-y-2 transition-[opacity,translate] duration-300 ease-thb18 group-has-checked:opacity-100 group-has-checked:translate-y-0 group-has-checked:[transition-delay:calc(120ms+var(--i)*40ms)] motion-reduce:translate-y-0 motion-reduce:duration-150"><a href="#beans" class="group/l flex items-center justify-between min-h-12 px-5 border-t border-[color-mix(in_oklch,var(--color-thb18-edge)_55%,transparent)] text-[14.5px] font-semibold text-thb18-mut no-underline transition-[background,color] duration-150 hover:bg-thb18-hover hover:text-thb18-ink focus-visible:bg-thb18-hover focus-visible:text-thb18-ink focus-visible:outline-2 focus-visible:outline-thb18-acc focus-visible:-outline-offset-2">Beans & gear<span class="opacity-0 -translate-x-1.5 transition-[opacity,translate] duration-200 group-hover/l:opacity-100 group-hover/l:translate-x-0 group-focus-visible/l:opacity-100 group-focus-visible/l:translate-x-0" aria-hidden="true">→</span></a></li>
<li style="--i:3" class="opacity-0 -translate-y-2 transition-[opacity,translate] duration-300 ease-thb18 group-has-checked:opacity-100 group-has-checked:translate-y-0 group-has-checked:[transition-delay:calc(120ms+var(--i)*40ms)] motion-reduce:translate-y-0 motion-reduce:duration-150"><a href="#wholesale" class="group/l flex items-center justify-between min-h-12 px-5 border-t border-[color-mix(in_oklch,var(--color-thb18-edge)_55%,transparent)] text-[14.5px] font-semibold text-thb18-mut no-underline transition-[background,color] duration-150 hover:bg-thb18-hover hover:text-thb18-ink focus-visible:bg-thb18-hover focus-visible:text-thb18-ink focus-visible:outline-2 focus-visible:outline-thb18-acc focus-visible:-outline-offset-2">Wholesale<span class="opacity-0 -translate-x-1.5 transition-[opacity,translate] duration-200 group-hover/l:opacity-100 group-hover/l:translate-x-0 group-focus-visible/l:opacity-100 group-focus-visible/l:translate-x-0" aria-hidden="true">→</span></a></li>
<li style="--i:4" class="opacity-0 -translate-y-2 transition-[opacity,translate] duration-300 ease-thb18 group-has-checked:opacity-100 group-has-checked:translate-y-0 group-has-checked:[transition-delay:calc(120ms+var(--i)*40ms)] motion-reduce:translate-y-0 motion-reduce:duration-150"><a href="#careers" class="group/l flex items-center justify-between min-h-12 px-5 border-t border-[color-mix(in_oklch,var(--color-thb18-edge)_55%,transparent)] text-[14.5px] font-semibold text-thb18-mut no-underline transition-[background,color] duration-150 hover:bg-thb18-hover hover:text-thb18-ink focus-visible:bg-thb18-hover focus-visible:text-thb18-ink focus-visible:outline-2 focus-visible:outline-thb18-acc focus-visible:-outline-offset-2">Careers<span class="opacity-0 -translate-x-1.5 transition-[opacity,translate] duration-200 group-hover/l:opacity-100 group-hover/l:translate-x-0 group-focus-visible/l:opacity-100 group-focus-visible/l:translate-x-0" aria-hidden="true">→</span></a></li>
</ul>
</nav>
</div>
<p class="w-[min(420px,100%)] text-center text-xs leading-relaxed text-thb18-mut text-pretty" aria-hidden="true">opens to <b class="font-semibold text-thb18-ink">height:auto</b> — grid <code class="font-thb18-mono text-[11px] px-1.5 py-0.5 rounded bg-thb18-code">0fr→1fr</code> base, <code class="font-thb18-mono text-[11px] px-1.5 py-0.5 rounded bg-thb18-code">interpolate-size</code> where supported. No max-height hack.</p>
</div>
</section>@import "tailwindcss";
@theme {
--color-thb18-bg: #faf8f5;
--color-thb18-bg: oklch(0.975 0.006 60);
--color-thb18-panel: #ffffff;
--color-thb18-panel: oklch(1 0 0);
--color-thb18-hover: #f6f3ee;
--color-thb18-hover: oklch(0.97 0.008 60);
--color-thb18-edge: #e0dbd2;
--color-thb18-edge: oklch(0.88 0.012 60);
--color-thb18-code: #ebe5da;
--color-thb18-code: oklch(0.92 0.01 60);
--color-thb18-ink: #3a3229;
--color-thb18-ink: oklch(0.25 0.02 45);
--color-thb18-mut: #776e62;
--color-thb18-mut: oklch(0.52 0.018 45);
--color-thb18-acc: #b8793c;
--color-thb18-acc: oklch(0.6 0.13 55);
--font-thb18-sans: 'Space Grotesk', system-ui, sans-serif;
--font-thb18-mono: 'JetBrains Mono', ui-monospace, monospace;
--ease-thb18: cubic-bezier(0.2, 0.7, 0.2, 1);
}
/* The interpolate-size alternative is deliberately NOT shipped here, and is written out in the
"How it works" and "Customize it" notes rather than as commented-out CSS: it swaps the panel's
display model from grid to block, so it REPLACES the row-track utilities instead of layering on
them. The grid technique is what ships — Baseline everywhere, and exact to content height. */
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's
w-full min-h-svh. It scopes the demo, and it holds the stage at full height in the
moment before the browser build has compiled its utilities. */
.thb-18 {
width: 100%;
min-height: 100svh;
}@import "tailwindcss";
@theme {
--color-thb18-bg: #faf8f5;
--color-thb18-bg: oklch(0.975 0.006 60);
--color-thb18-panel: #ffffff;
--color-thb18-panel: oklch(1 0 0);
--color-thb18-hover: #f6f3ee;
--color-thb18-hover: oklch(0.97 0.008 60);
--color-thb18-edge: #e0dbd2;
--color-thb18-edge: oklch(0.88 0.012 60);
--color-thb18-code: #ebe5da;
--color-thb18-code: oklch(0.92 0.01 60);
--color-thb18-ink: #3a3229;
--color-thb18-ink: oklch(0.25 0.02 45);
--color-thb18-mut: #776e62;
--color-thb18-mut: oklch(0.52 0.018 45);
--color-thb18-acc: #b8793c;
--color-thb18-acc: oklch(0.6 0.13 55);
--font-thb18-sans: 'Space Grotesk', system-ui, sans-serif;
--font-thb18-mono: 'JetBrains Mono', ui-monospace, monospace;
--ease-thb18: cubic-bezier(0.2, 0.7, 0.2, 1);
}
/* The interpolate-size alternative is deliberately NOT shipped here, and is written out in the
"How it works" and "Customize it" notes rather than as commented-out CSS: it swaps the panel's
display model from grid to block, so it REPLACES the row-track utilities instead of layering on
them. The grid technique is what ships — Baseline everywhere, and exact to content height. */
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's
w-full min-h-svh. It scopes the demo, and it holds the stage at full height in the
moment before the browser build has compiled its utilities. */
.thb-18 {
width: 100%;
min-height: 100svh;
}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-thb18-bg: #faf8f5;
--color-thb18-bg: oklch(0.975 0.006 60);
--color-thb18-panel: #ffffff;
--color-thb18-panel: oklch(1 0 0);
--color-thb18-hover: #f6f3ee;
--color-thb18-hover: oklch(0.97 0.008 60);
--color-thb18-edge: #e0dbd2;
--color-thb18-edge: oklch(0.88 0.012 60);
--color-thb18-code: #ebe5da;
--color-thb18-code: oklch(0.92 0.01 60);
--color-thb18-ink: #3a3229;
--color-thb18-ink: oklch(0.25 0.02 45);
--color-thb18-mut: #776e62;
--color-thb18-mut: oklch(0.52 0.018 45);
--color-thb18-acc: #b8793c;
--color-thb18-acc: oklch(0.6 0.13 55);
--font-thb18-sans: 'Space Grotesk', system-ui, sans-serif;
--font-thb18-mono: 'JetBrains Mono', ui-monospace, monospace;
--ease-thb18: cubic-bezier(0.2, 0.7, 0.2, 1);
}
/* The interpolate-size alternative is deliberately NOT shipped here, and is written out in the
"How it works" and "Customize it" notes rather than as commented-out CSS: it swaps the panel's
display model from grid to block, so it REPLACES the row-track utilities instead of layering on
them. The grid technique is what ships — Baseline everywhere, and exact to content height. */
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's
w-full min-h-svh. It scopes the demo, and it holds the stage at full height in the
moment before the browser build has compiled its utilities. */
.thb-18 {
width: 100%;
min-height: 100svh;
}@import "tailwindcss";
@theme {
--color-thb18-bg: #faf8f5;
--color-thb18-bg: oklch(0.975 0.006 60);
--color-thb18-panel: #ffffff;
--color-thb18-panel: oklch(1 0 0);
--color-thb18-hover: #f6f3ee;
--color-thb18-hover: oklch(0.97 0.008 60);
--color-thb18-edge: #e0dbd2;
--color-thb18-edge: oklch(0.88 0.012 60);
--color-thb18-code: #ebe5da;
--color-thb18-code: oklch(0.92 0.01 60);
--color-thb18-ink: #3a3229;
--color-thb18-ink: oklch(0.25 0.02 45);
--color-thb18-mut: #776e62;
--color-thb18-mut: oklch(0.52 0.018 45);
--color-thb18-acc: #b8793c;
--color-thb18-acc: oklch(0.6 0.13 55);
--font-thb18-sans: 'Space Grotesk', system-ui, sans-serif;
--font-thb18-mono: 'JetBrains Mono', ui-monospace, monospace;
--ease-thb18: cubic-bezier(0.2, 0.7, 0.2, 1);
}
/* The interpolate-size alternative is deliberately NOT shipped here, and is written out in the
"How it works" and "Customize it" notes rather than as commented-out CSS: it swaps the panel's
display model from grid to block, so it REPLACES the row-track utilities instead of layering on
them. The grid technique is what ships — Baseline everywhere, and exact to content height. */
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's
w-full min-h-svh. It scopes the demo, and it holds the stage at full height in the
moment before the browser build has compiled its utilities. */
.thb-18 {
width: 100%;
min-height: 100svh;
}<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=Space+Grotesk:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&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=Space+Grotesk:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
- Both height techniques survive, but the port made their relationship honest: they are alternatives, not layers. The grid pair becomes two arbitrary utilities on the panel; the
interpolate-sizeversion needsdisplay:block, which overrides those utilities rather than enhancing them, so it ships as a commented recipe with a note to remove the grid classes if you switch. :checked ~ .head .burger ibecomesgroup-has-checked:on the bars. This is the demo where the difference betweenpeer-*(siblings only) andgroup-has-*(any descendant) decides the markup.min-h-0 overflow-hiddensit together on the list, which makes the shrink permit visible at the point of use instead of three rules away.- The item stagger drops its
:checked ~rule for the--iindex pattern and one[transition-delay:calc(…)]. - The
::afterarrow becomes a real span driven bygroup/l, so hover and focus-visible are declared together and cannot diverge. - Zero JavaScript, and the padding-on-items rule (never on the collapsing container) is now enforced by where the utilities sit rather than by a comment.
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: Pure Tailwind Dropdown Slide Down Mobile Hamburger Menu
Source: https://codefronts.com/navigation/tailwind-hamburger-menus/pure-tailwind-dropdown-slide-down-mobile-hamburger-menu/
How to animate a menu to height:auto — the question with a decade of bad answers — solved twice in utilities: the [grid-template-rows:0fr] → 1fr technique that is Baseline everywhere, and the 2026-native interpolate-size: allow-keywords written out beside it as the alternative. They are alternatives, not layers — the second one swaps the panel's display model, so this demo ships the grid version and documents the other. Zero JavaScript.
## HTML
```html
<section class="thb-18 w-full min-h-svh bg-thb18-bg font-thb18-sans text-thb18-ink px-5 py-9" aria-label="Slide down dropdown hamburger menu demo">
<div class="min-h-100 flex flex-col items-center justify-center gap-4 p-6.5 rounded-[18px] border border-thb18-edge bg-thb18-bg">
<div class="group relative w-[min(420px,100%)] overflow-hidden rounded-2xl border border-thb18-edge bg-thb18-panel shadow-[0_16px_38px_-28px_oklch(0.35_0.06_50/0.55)]">
<input type="checkbox" id="thb18-t" class="peer absolute w-px h-px overflow-hidden whitespace-nowrap [clip-path:inset(50%)]">
<header class="flex items-center justify-between p-3 pl-5">
<span class="flex items-center gap-2.5 text-[15.5px] font-extrabold tracking-[-0.01em]">
<i class="w-5 h-5 rounded-full bg-[radial-gradient(circle_at_35%_30%,oklch(0.8_0.1_70),var(--color-thb18-acc)_70%)]" aria-hidden="true"></i>brewline
</span>
<label for="thb18-t" class="grid place-items-center w-11.5 h-11.5 rounded-xl border border-thb18-edge cursor-pointer select-none transition-[background,border-color] duration-200 hover:bg-thb18-hover peer-focus-visible:outline-2 peer-focus-visible:outline-thb18-acc peer-focus-visible:outline-offset-2">
<span class="relative block w-4.5 h-3" aria-hidden="true">
<i class="absolute left-0 top-0 w-4.5 h-0.5 rounded-sm bg-thb18-ink transition-[translate,rotate] duration-280 group-has-checked:translate-y-[5px] group-has-checked:rotate-45 motion-reduce:transition-none"></i>
<i class="absolute left-0 top-[5px] w-4.5 h-0.5 rounded-sm bg-thb18-ink transition-[opacity] duration-200 group-has-checked:opacity-0 motion-reduce:transition-none"></i>
<i class="absolute left-0 top-2.5 w-4.5 h-0.5 rounded-sm bg-thb18-ink transition-[translate,rotate] duration-280 group-has-checked:-translate-y-[5px] group-has-checked:-rotate-45 motion-reduce:transition-none"></i>
</span>
</label>
</header>
<nav aria-label="Primary" class="thb18-panel grid [grid-template-rows:0fr] transition-[grid-template-rows] duration-400 ease-thb18 group-has-checked:[grid-template-rows:1fr] motion-reduce:transition-none">
<ul class="list-none min-h-0 overflow-hidden">
<li style="--i:0" class="opacity-0 -translate-y-2 transition-[opacity,translate] duration-300 ease-thb18 group-has-checked:opacity-100 group-has-checked:translate-y-0 group-has-checked:[transition-delay:calc(120ms+var(--i)*40ms)] motion-reduce:translate-y-0 motion-reduce:duration-150"><a href="#menu" aria-current="page" class="group/l flex items-center justify-between min-h-12 px-5 border-t border-[color-mix(in_oklch,var(--color-thb18-edge)_55%,transparent)] text-[14.5px] font-semibold text-thb18-mut no-underline transition-[background,color] duration-150 hover:bg-thb18-hover hover:text-thb18-ink focus-visible:bg-thb18-hover focus-visible:text-thb18-ink focus-visible:outline-2 focus-visible:outline-thb18-acc focus-visible:-outline-offset-2 aria-current:text-thb18-acc">Menu<span class="opacity-0 -translate-x-1.5 transition-[opacity,translate] duration-200 group-hover/l:opacity-100 group-hover/l:translate-x-0 group-focus-visible/l:opacity-100 group-focus-visible/l:translate-x-0" aria-hidden="true">→</span></a></li>
<li style="--i:1" class="opacity-0 -translate-y-2 transition-[opacity,translate] duration-300 ease-thb18 group-has-checked:opacity-100 group-has-checked:translate-y-0 group-has-checked:[transition-delay:calc(120ms+var(--i)*40ms)] motion-reduce:translate-y-0 motion-reduce:duration-150"><a href="#locations" class="group/l flex items-center justify-between min-h-12 px-5 border-t border-[color-mix(in_oklch,var(--color-thb18-edge)_55%,transparent)] text-[14.5px] font-semibold text-thb18-mut no-underline transition-[background,color] duration-150 hover:bg-thb18-hover hover:text-thb18-ink focus-visible:bg-thb18-hover focus-visible:text-thb18-ink focus-visible:outline-2 focus-visible:outline-thb18-acc focus-visible:-outline-offset-2">Locations<span class="opacity-0 -translate-x-1.5 transition-[opacity,translate] duration-200 group-hover/l:opacity-100 group-hover/l:translate-x-0 group-focus-visible/l:opacity-100 group-focus-visible/l:translate-x-0" aria-hidden="true">→</span></a></li>
<li style="--i:2" class="opacity-0 -translate-y-2 transition-[opacity,translate] duration-300 ease-thb18 group-has-checked:opacity-100 group-has-checked:translate-y-0 group-has-checked:[transition-delay:calc(120ms+var(--i)*40ms)] motion-reduce:translate-y-0 motion-reduce:duration-150"><a href="#beans" class="group/l flex items-center justify-between min-h-12 px-5 border-t border-[color-mix(in_oklch,var(--color-thb18-edge)_55%,transparent)] text-[14.5px] font-semibold text-thb18-mut no-underline transition-[background,color] duration-150 hover:bg-thb18-hover hover:text-thb18-ink focus-visible:bg-thb18-hover focus-visible:text-thb18-ink focus-visible:outline-2 focus-visible:outline-thb18-acc focus-visible:-outline-offset-2">Beans & gear<span class="opacity-0 -translate-x-1.5 transition-[opacity,translate] duration-200 group-hover/l:opacity-100 group-hover/l:translate-x-0 group-focus-visible/l:opacity-100 group-focus-visible/l:translate-x-0" aria-hidden="true">→</span></a></li>
<li style="--i:3" class="opacity-0 -translate-y-2 transition-[opacity,translate] duration-300 ease-thb18 group-has-checked:opacity-100 group-has-checked:translate-y-0 group-has-checked:[transition-delay:calc(120ms+var(--i)*40ms)] motion-reduce:translate-y-0 motion-reduce:duration-150"><a href="#wholesale" class="group/l flex items-center justify-between min-h-12 px-5 border-t border-[color-mix(in_oklch,var(--color-thb18-edge)_55%,transparent)] text-[14.5px] font-semibold text-thb18-mut no-underline transition-[background,color] duration-150 hover:bg-thb18-hover hover:text-thb18-ink focus-visible:bg-thb18-hover focus-visible:text-thb18-ink focus-visible:outline-2 focus-visible:outline-thb18-acc focus-visible:-outline-offset-2">Wholesale<span class="opacity-0 -translate-x-1.5 transition-[opacity,translate] duration-200 group-hover/l:opacity-100 group-hover/l:translate-x-0 group-focus-visible/l:opacity-100 group-focus-visible/l:translate-x-0" aria-hidden="true">→</span></a></li>
<li style="--i:4" class="opacity-0 -translate-y-2 transition-[opacity,translate] duration-300 ease-thb18 group-has-checked:opacity-100 group-has-checked:translate-y-0 group-has-checked:[transition-delay:calc(120ms+var(--i)*40ms)] motion-reduce:translate-y-0 motion-reduce:duration-150"><a href="#careers" class="group/l flex items-center justify-between min-h-12 px-5 border-t border-[color-mix(in_oklch,var(--color-thb18-edge)_55%,transparent)] text-[14.5px] font-semibold text-thb18-mut no-underline transition-[background,color] duration-150 hover:bg-thb18-hover hover:text-thb18-ink focus-visible:bg-thb18-hover focus-visible:text-thb18-ink focus-visible:outline-2 focus-visible:outline-thb18-acc focus-visible:-outline-offset-2">Careers<span class="opacity-0 -translate-x-1.5 transition-[opacity,translate] duration-200 group-hover/l:opacity-100 group-hover/l:translate-x-0 group-focus-visible/l:opacity-100 group-focus-visible/l:translate-x-0" aria-hidden="true">→</span></a></li>
</ul>
</nav>
</div>
<p class="w-[min(420px,100%)] text-center text-xs leading-relaxed text-thb18-mut text-pretty" aria-hidden="true">opens to <b class="font-semibold text-thb18-ink">height:auto</b> — grid <code class="font-thb18-mono text-[11px] px-1.5 py-0.5 rounded bg-thb18-code">0fr→1fr</code> base, <code class="font-thb18-mono text-[11px] px-1.5 py-0.5 rounded bg-thb18-code">interpolate-size</code> where supported. No max-height hack.</p>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-thb18-bg: #faf8f5;
--color-thb18-bg: oklch(0.975 0.006 60);
--color-thb18-panel: #ffffff;
--color-thb18-panel: oklch(1 0 0);
--color-thb18-hover: #f6f3ee;
--color-thb18-hover: oklch(0.97 0.008 60);
--color-thb18-edge: #e0dbd2;
--color-thb18-edge: oklch(0.88 0.012 60);
--color-thb18-code: #ebe5da;
--color-thb18-code: oklch(0.92 0.01 60);
--color-thb18-ink: #3a3229;
--color-thb18-ink: oklch(0.25 0.02 45);
--color-thb18-mut: #776e62;
--color-thb18-mut: oklch(0.52 0.018 45);
--color-thb18-acc: #b8793c;
--color-thb18-acc: oklch(0.6 0.13 55);
--font-thb18-sans: 'Space Grotesk', system-ui, sans-serif;
--font-thb18-mono: 'JetBrains Mono', ui-monospace, monospace;
--ease-thb18: cubic-bezier(0.2, 0.7, 0.2, 1);
}
/* The interpolate-size alternative is deliberately NOT shipped here, and is written out in the
"How it works" and "Customize it" notes rather than as commented-out CSS: it swaps the panel's
display model from grid to block, so it REPLACES the row-track utilities instead of layering on
them. The grid technique is what ships — Baseline everywhere, and exact to content height. */
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's
w-full min-h-svh. It scopes the demo, and it holds the stage at full height in the
moment before the browser build has compiled its utilities. */
.thb-18 {
width: 100%;
min-height: 100svh;
}
```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: Pure Tailwind Dropdown Slide Down Mobile Hamburger Menu
Source: https://codefronts.com/navigation/tailwind-hamburger-menus/pure-tailwind-dropdown-slide-down-mobile-hamburger-menu/
How to animate a menu to height:auto — the question with a decade of bad answers — solved twice in utilities: the [grid-template-rows:0fr] → 1fr technique that is Baseline everywhere, and the 2026-native interpolate-size: allow-keywords written out beside it as the alternative. They are alternatives, not layers — the second one swaps the panel's display model, so this demo ships the grid version and documents the other. Zero JavaScript.
## HTML
```html
<section class="thb-18 w-full min-h-svh bg-thb18-bg font-thb18-sans text-thb18-ink px-5 py-9" aria-label="Slide down dropdown hamburger menu demo">
<div class="min-h-100 flex flex-col items-center justify-center gap-4 p-6.5 rounded-[18px] border border-thb18-edge bg-thb18-bg">
<div class="group relative w-[min(420px,100%)] overflow-hidden rounded-2xl border border-thb18-edge bg-thb18-panel shadow-[0_16px_38px_-28px_oklch(0.35_0.06_50/0.55)]">
<input type="checkbox" id="thb18-t" class="peer absolute w-px h-px overflow-hidden whitespace-nowrap [clip-path:inset(50%)]">
<header class="flex items-center justify-between p-3 pl-5">
<span class="flex items-center gap-2.5 text-[15.5px] font-extrabold tracking-[-0.01em]">
<i class="w-5 h-5 rounded-full bg-[radial-gradient(circle_at_35%_30%,oklch(0.8_0.1_70),var(--color-thb18-acc)_70%)]" aria-hidden="true"></i>brewline
</span>
<label for="thb18-t" class="grid place-items-center w-11.5 h-11.5 rounded-xl border border-thb18-edge cursor-pointer select-none transition-[background,border-color] duration-200 hover:bg-thb18-hover peer-focus-visible:outline-2 peer-focus-visible:outline-thb18-acc peer-focus-visible:outline-offset-2">
<span class="relative block w-4.5 h-3" aria-hidden="true">
<i class="absolute left-0 top-0 w-4.5 h-0.5 rounded-sm bg-thb18-ink transition-[translate,rotate] duration-280 group-has-checked:translate-y-[5px] group-has-checked:rotate-45 motion-reduce:transition-none"></i>
<i class="absolute left-0 top-[5px] w-4.5 h-0.5 rounded-sm bg-thb18-ink transition-[opacity] duration-200 group-has-checked:opacity-0 motion-reduce:transition-none"></i>
<i class="absolute left-0 top-2.5 w-4.5 h-0.5 rounded-sm bg-thb18-ink transition-[translate,rotate] duration-280 group-has-checked:-translate-y-[5px] group-has-checked:-rotate-45 motion-reduce:transition-none"></i>
</span>
</label>
</header>
<nav aria-label="Primary" class="thb18-panel grid [grid-template-rows:0fr] transition-[grid-template-rows] duration-400 ease-thb18 group-has-checked:[grid-template-rows:1fr] motion-reduce:transition-none">
<ul class="list-none min-h-0 overflow-hidden">
<li style="--i:0" class="opacity-0 -translate-y-2 transition-[opacity,translate] duration-300 ease-thb18 group-has-checked:opacity-100 group-has-checked:translate-y-0 group-has-checked:[transition-delay:calc(120ms+var(--i)*40ms)] motion-reduce:translate-y-0 motion-reduce:duration-150"><a href="#menu" aria-current="page" class="group/l flex items-center justify-between min-h-12 px-5 border-t border-[color-mix(in_oklch,var(--color-thb18-edge)_55%,transparent)] text-[14.5px] font-semibold text-thb18-mut no-underline transition-[background,color] duration-150 hover:bg-thb18-hover hover:text-thb18-ink focus-visible:bg-thb18-hover focus-visible:text-thb18-ink focus-visible:outline-2 focus-visible:outline-thb18-acc focus-visible:-outline-offset-2 aria-current:text-thb18-acc">Menu<span class="opacity-0 -translate-x-1.5 transition-[opacity,translate] duration-200 group-hover/l:opacity-100 group-hover/l:translate-x-0 group-focus-visible/l:opacity-100 group-focus-visible/l:translate-x-0" aria-hidden="true">→</span></a></li>
<li style="--i:1" class="opacity-0 -translate-y-2 transition-[opacity,translate] duration-300 ease-thb18 group-has-checked:opacity-100 group-has-checked:translate-y-0 group-has-checked:[transition-delay:calc(120ms+var(--i)*40ms)] motion-reduce:translate-y-0 motion-reduce:duration-150"><a href="#locations" class="group/l flex items-center justify-between min-h-12 px-5 border-t border-[color-mix(in_oklch,var(--color-thb18-edge)_55%,transparent)] text-[14.5px] font-semibold text-thb18-mut no-underline transition-[background,color] duration-150 hover:bg-thb18-hover hover:text-thb18-ink focus-visible:bg-thb18-hover focus-visible:text-thb18-ink focus-visible:outline-2 focus-visible:outline-thb18-acc focus-visible:-outline-offset-2">Locations<span class="opacity-0 -translate-x-1.5 transition-[opacity,translate] duration-200 group-hover/l:opacity-100 group-hover/l:translate-x-0 group-focus-visible/l:opacity-100 group-focus-visible/l:translate-x-0" aria-hidden="true">→</span></a></li>
<li style="--i:2" class="opacity-0 -translate-y-2 transition-[opacity,translate] duration-300 ease-thb18 group-has-checked:opacity-100 group-has-checked:translate-y-0 group-has-checked:[transition-delay:calc(120ms+var(--i)*40ms)] motion-reduce:translate-y-0 motion-reduce:duration-150"><a href="#beans" class="group/l flex items-center justify-between min-h-12 px-5 border-t border-[color-mix(in_oklch,var(--color-thb18-edge)_55%,transparent)] text-[14.5px] font-semibold text-thb18-mut no-underline transition-[background,color] duration-150 hover:bg-thb18-hover hover:text-thb18-ink focus-visible:bg-thb18-hover focus-visible:text-thb18-ink focus-visible:outline-2 focus-visible:outline-thb18-acc focus-visible:-outline-offset-2">Beans & gear<span class="opacity-0 -translate-x-1.5 transition-[opacity,translate] duration-200 group-hover/l:opacity-100 group-hover/l:translate-x-0 group-focus-visible/l:opacity-100 group-focus-visible/l:translate-x-0" aria-hidden="true">→</span></a></li>
<li style="--i:3" class="opacity-0 -translate-y-2 transition-[opacity,translate] duration-300 ease-thb18 group-has-checked:opacity-100 group-has-checked:translate-y-0 group-has-checked:[transition-delay:calc(120ms+var(--i)*40ms)] motion-reduce:translate-y-0 motion-reduce:duration-150"><a href="#wholesale" class="group/l flex items-center justify-between min-h-12 px-5 border-t border-[color-mix(in_oklch,var(--color-thb18-edge)_55%,transparent)] text-[14.5px] font-semibold text-thb18-mut no-underline transition-[background,color] duration-150 hover:bg-thb18-hover hover:text-thb18-ink focus-visible:bg-thb18-hover focus-visible:text-thb18-ink focus-visible:outline-2 focus-visible:outline-thb18-acc focus-visible:-outline-offset-2">Wholesale<span class="opacity-0 -translate-x-1.5 transition-[opacity,translate] duration-200 group-hover/l:opacity-100 group-hover/l:translate-x-0 group-focus-visible/l:opacity-100 group-focus-visible/l:translate-x-0" aria-hidden="true">→</span></a></li>
<li style="--i:4" class="opacity-0 -translate-y-2 transition-[opacity,translate] duration-300 ease-thb18 group-has-checked:opacity-100 group-has-checked:translate-y-0 group-has-checked:[transition-delay:calc(120ms+var(--i)*40ms)] motion-reduce:translate-y-0 motion-reduce:duration-150"><a href="#careers" class="group/l flex items-center justify-between min-h-12 px-5 border-t border-[color-mix(in_oklch,var(--color-thb18-edge)_55%,transparent)] text-[14.5px] font-semibold text-thb18-mut no-underline transition-[background,color] duration-150 hover:bg-thb18-hover hover:text-thb18-ink focus-visible:bg-thb18-hover focus-visible:text-thb18-ink focus-visible:outline-2 focus-visible:outline-thb18-acc focus-visible:-outline-offset-2">Careers<span class="opacity-0 -translate-x-1.5 transition-[opacity,translate] duration-200 group-hover/l:opacity-100 group-hover/l:translate-x-0 group-focus-visible/l:opacity-100 group-focus-visible/l:translate-x-0" aria-hidden="true">→</span></a></li>
</ul>
</nav>
</div>
<p class="w-[min(420px,100%)] text-center text-xs leading-relaxed text-thb18-mut text-pretty" aria-hidden="true">opens to <b class="font-semibold text-thb18-ink">height:auto</b> — grid <code class="font-thb18-mono text-[11px] px-1.5 py-0.5 rounded bg-thb18-code">0fr→1fr</code> base, <code class="font-thb18-mono text-[11px] px-1.5 py-0.5 rounded bg-thb18-code">interpolate-size</code> where supported. No max-height hack.</p>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-thb18-bg: #faf8f5;
--color-thb18-bg: oklch(0.975 0.006 60);
--color-thb18-panel: #ffffff;
--color-thb18-panel: oklch(1 0 0);
--color-thb18-hover: #f6f3ee;
--color-thb18-hover: oklch(0.97 0.008 60);
--color-thb18-edge: #e0dbd2;
--color-thb18-edge: oklch(0.88 0.012 60);
--color-thb18-code: #ebe5da;
--color-thb18-code: oklch(0.92 0.01 60);
--color-thb18-ink: #3a3229;
--color-thb18-ink: oklch(0.25 0.02 45);
--color-thb18-mut: #776e62;
--color-thb18-mut: oklch(0.52 0.018 45);
--color-thb18-acc: #b8793c;
--color-thb18-acc: oklch(0.6 0.13 55);
--font-thb18-sans: 'Space Grotesk', system-ui, sans-serif;
--font-thb18-mono: 'JetBrains Mono', ui-monospace, monospace;
--ease-thb18: cubic-bezier(0.2, 0.7, 0.2, 1);
}
/* The interpolate-size alternative is deliberately NOT shipped here, and is written out in the
"How it works" and "Customize it" notes rather than as commented-out CSS: it swaps the panel's
display model from grid to block, so it REPLACES the row-track utilities instead of layering on
them. The grid technique is what ships — Baseline everywhere, and exact to content height. */
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's
w-full min-h-svh. It scopes the demo, and it holds the stage at full height in the
moment before the browser build has compiled its utilities. */
.thb-18 {
width: 100%;
min-height: 100svh;
}
```How this works
Why height:0 → auto never worked, and what does. auto is not a number, so it cannot interpolate; the old max-height hack eases toward a fake number, which is why opening lags and closing snaps. Fractions of a grid track ARE interpolable:
<nav class="grid [grid-template-rows:0fr]
transition-[grid-template-rows] duration-400 ease-thb18
group-has-checked:[grid-template-rows:1fr]">
<ul class="min-h-0 overflow-hidden">…</ul>
</nav>The child pair is load-bearing: overflow-hidden clips content while the track is fractional, and min-h-0 waives the default “never smaller than content” rule. The browser then interpolates against the content's real height — exact easing, any content, no magic numbers.
The native ending, and why it is not stacked on top. interpolate-size: allow-keywords opts a subtree into animating to intrinsic sizes, after which height: 0 → height: auto simply transitions. But it only works by turning the panel back into a block box, which means it REPLACES the grid utilities instead of enhancing them — leave both in and the block rules win the cascade while the row-track utilities do nothing. So it is documented, not shipped: to switch, set interpolate-size: allow-keywords on the demo root, give .thb18-panel display:block; height:0; overflow:hidden with a height transition, set height:auto in the checked state — and delete the grid utilities from the panel's class list. The grid version ships because it is Baseline and needs no feature query at all.
Why the state variant is group-has-checked:. The panel is a sibling of the checkbox, so peer-checked: would work for it — but the burger's three bars live inside a <label>, and peer-* cannot reach a sibling's descendants. One group on the bar covers both.
Make it yours
- Duration scales with content:
duration-300suits four links,duration-450suits eight. The technique always lands exactly at content height; only the tempo is yours. - The panel is width-locked to the bar because it is inside it. Make it
absoluteunder the header for a floating dropdown — and add a shadow, since it now floats. - Chevron variant: swap the three bars for one chevron and rotate it with
group-has-checked:rotate-180. calc-size()(same engine asinterpolate-size) animates to “auto plus padding”:height: calc-size(auto, size + 12px), useful for a menu with a footer fade.- Retheme through
--color-thb18-acc, which feeds the brand mark, the current-page link and every focus ring.
Gotchas — read before shipping
- The
0frtrick needsmin-h-0 overflow-hiddenon the DIRECT child. Putting them on the grid itself does nothing: the track shrinks and the content pokes out. - Percentage and vertical padding inside the collapsing child do not collapse — they survive at
0fras visible slivers. Pad the items, not the container. interpolate-sizeis inherited from wherever you set it. Scope it to the component root, not:root, or unrelatedheight:autotransitions elsewhere start animating.- Do not combine the grid technique with a
max-h-*cap “for safety” — the cap re-introduces the exact timing lag the grid method exists to kill. The same applies to stackinginterpolate-sizeon top: itsdisplay:blocksilently disables the row-track utilities, and you end up with a panel sized by whichever rule wins. peer-checked:cannot style the label's inner bars. Usegroup-has-checked:from a common ancestor, as here.- Keep the checkbox clipped, not
hidden: it is the only keyboard affordance this pattern has.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 129+ | not yet | not yet | 129+ |
Floor set by oklch(), color-mix(), interpolate-size as this demo is written. The core technique itself goes back further — Chrome 117+.
The grid-template-rows transition is Baseline (Chrome 107, Safari 16, Firefox 66). interpolate-size / calc-size() is Chrome 129+ and rolling out elsewhere, which is why it rides behind @supports; non-supporting engines never see it.