28 Tailwind Hamburger Menus
18 / 28

All 28 in 28 Tailwind Hamburger Menus

  1. 01Tailwind Hamburger Menu with Clip-Path Circle Expansion
  2. 02Tailwind Keyboard Navigable Hamburger Menu with ARIA Attributes
  3. 03Tailwind Animated 3-Line to X Hamburger Icon Toggle
  4. 04Tailwind Slide-In Off-Canvas Drawer with Dimmed Backdrop Overlay
  5. 05Pure Tailwind Checkbox Hack Hamburger Navigation (No JavaScript)
  6. 06Tailwind Responsive Hamburger Menu with Integrated Search Bar & Social Links
  7. 07Tailwind Glassmorphism Backdrop Blur Hamburger Navigation
  8. 08Tailwind Push Page Navigation Menu (Off-Canvas Content Shift)
  9. 09Pure Tailwind Hamburger Menu Without JavaScript
  10. 10Tailwind Hamburger Menu Morphing to Close Icon Animation
  11. 11Tailwind Full Screen Mobile Navigation Overlay
  12. 12Tailwind Slide Out Sidebar Navigation / Off Canvas Menu
  13. 13Tailwind Responsive Navbar Mobile Hamburger Toggle Breakpoints
  14. 14Tailwind Minimalist Floating Hamburger Button
  15. 15Tailwind CSS Responsive Hamburger Menu Component
  16. 16Tailwind Rebuild of the Bootstrap 5 Navbar Hamburger Toggle
  17. 17Tailwind WCAG Accessible Hamburger Menu Example
  18. 18Pure Tailwind Dropdown Slide Down Mobile Hamburger Menu
  19. 19Simple 3-Line Tailwind Hamburger Icon Hover Effect
  20. 20Tailwind Full Screen Blur Overlay Hamburger Menu
  21. 21Tailwind Mobile Bottom Navigation Bar with Floating Hamburger Toggle
  22. 22Pure Tailwind Multi-Level Accordion Hamburger Dropdown Menu
  23. 23Flexbox Navbar Right-Aligned Tailwind Hamburger Icon
  24. 24Radial Ripple Expand Tailwind Hamburger Menu
  25. 25Sticky Tailwind Navbar Shrinking Hamburger Icon on Scroll
  26. 26Neumorphic Tailwind Hamburger Toggle Button
  27. 27Brutalist Design Bordered Tailwind Hamburger Menu
  28. 28Tailwind 3D Flip Canvas Mobile Hamburger Navigation
Pure TailwindMIT licensed

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.

Live Demo
Plain CSSTailwind v4
Try it

Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time

Untitled UI React · Affiliate link

Get Untitled UI Pro

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">&rarr;</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">&rarr;</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 &amp; 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">&rarr;</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">&rarr;</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">&rarr;</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> &mdash; grid <code class="font-thb18-mono text-[11px] px-1.5 py-0.5 rounded bg-thb18-code">0fr&rarr;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;
}

Setup — do this once per project

Skip this if your project already runs Tailwind v4.

1Load Tailwind
<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.

2Add the stylesheetRequiredthe @theme block is what makes the custom utilities exist
@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;
}
3Fonts used by this demooptional — skip if you have your own type stack
<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-size version needs display: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 i becomes group-has-checked: on the bars. This is the demo where the difference between peer-* (siblings only) and group-has-* (any descendant) decides the markup.
  • min-h-0 overflow-hidden sit 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 --i index pattern and one [transition-delay:calc(…)].
  • The ::after arrow becomes a real span driven by group/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.
Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
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 &mdash; the question with a decade of bad answers &mdash; solved twice in utilities: the [grid-template-rows:0fr] &rarr; 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 &mdash; 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">&rarr;</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">&rarr;</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 &amp; 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">&rarr;</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">&rarr;</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">&rarr;</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> &mdash; grid <code class="font-thb18-mono text-[11px] px-1.5 py-0.5 rounded bg-thb18-code">0fr&rarr;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-300 suits four links, duration-450 suits 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 absolute under 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 as interpolate-size) animates to &ldquo;auto plus padding&rdquo;: 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 0fr trick needs min-h-0 overflow-hidden on 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 0fr as visible slivers. Pad the items, not the container.
  • interpolate-size is inherited from wherever you set it. Scope it to the component root, not :root, or unrelated height:auto transitions elsewhere start animating.
  • Do not combine the grid technique with a max-h-* cap &ldquo;for safety&rdquo; — the cap re-introduces the exact timing lag the grid method exists to kill. The same applies to stacking interpolate-size on top: its display:block silently 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. Use group-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

ChromeSafariFirefoxEdge
129+not yetnot yet129+

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.

Techniques used in this demo

Theme tokensset in @theme, used by a utility

Search CodeFronts

Loading…