03 / 25
All 25 in 25 Tailwind CSS Sticky Navigations
- 01Tailwind CSS Sticky Navbar (No JavaScript, sticky top-0)
- 02Tailwind Sticky Header with Flexbox (Logo, Links, CTA)
- 03Responsive Tailwind Sticky Navbar with Hamburger Menu
- 04Tailwind Full-Width Sticky Header with Max-Width Container
- 05Tailwind Floating Glass Pill Navbar (Backdrop Blur)
- 06Tailwind Sticky Header Shrink on Scroll
- 07Tailwind Hide on Scroll Down, Show on Scroll Up Navbar
- 08Tailwind Transparent to Solid Navbar on Scroll
- 09Tailwind Sticky Header with Reading Progress Bar
- 10Tailwind Sticky Logo Shrink: Wordmark to Icon on Scroll
- 11Tailwind Navbar with Sliding Underline Active Link
- 12Tailwind Dark Mode Sticky Navbar with Theme Toggle
- 13Minimal Tailwind Sticky Header with Single CTA Button
- 14Tailwind Split Navbar with Centered Logo
- 15Neumorphic Sticky Navbar in Tailwind CSS
- 16Tailwind Sticky Header with Mega Menu Dropdown
- 17Tailwind E-Commerce Header with Search Bar and Cart Badge
- 18Tailwind Sticky Announcement Bar Above Navbar
- 19Tailwind Sticky Sub-Navigation Bar Under Main Header
- 20Tailwind Expandable Search Icon Navbar
- 21Tailwind Sticky Sidebar Table of Contents
- 22Tailwind Mobile Bottom Navigation Bar with Safe Area
- 23Tailwind Scrollspy Navbar: Highlight Active Section
- 24Tailwind Scrollable Sticky Tabs with Scroll Snap
- 25Tailwind Sticky Header with Social Share Buttons
Responsive Tailwind Sticky Navbar with Hamburger Menu
The responsive header with a hamburger that morphs into an X and a menu that opens by animating grid-rows-[0fr] to grid-rows-[1fr] — true height animation without measuring. A nine-line script sets one data-open attribute; every visual state is a group-data-open/hdr: variant. Escape closes, focus returns, and the closed panel is inert.
Complete Tailwind CSS landing page templates in HTML, React, Next.js and Vue · from $49
Cruip · Affiliate link
The code
<div class="tsn-03 w-full min-h-svh bg-tsn03-bg font-tsn03-sans text-tsn03-ink antialiased" aria-label="Responsive Tailwind hamburger navbar demo">
<header data-tsn03-header class="group/hdr sticky top-0 z-40 border-b border-tsn03-line bg-tsn03-bg/85 backdrop-blur-xl">
<div class="flex h-16 items-center gap-6 px-[max(1.25rem,calc((100%-72rem)/2))]">
<a href="#tsn-03-s1" class="flex min-h-11 items-center gap-2 rounded-lg text-lg font-bold tracking-[-0.03em] focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-tsn03-acc"><span aria-hidden="true" class="size-3 rotate-45 rounded-[3px] bg-tsn03-acc"></span>Fieldnote</a>
<nav aria-label="Primary" class="ms-auto flex gap-1 max-md:hidden">
<a href="#tsn-03-s1" aria-current="page" class="inline-flex min-h-11 items-center rounded-full px-4 text-sm font-medium text-tsn03-mut hover:text-tsn03-ink focus-visible:outline-2 focus-visible:outline-tsn03-acc aria-[current=page]:bg-tsn03-ink/8 aria-[current=page]:text-tsn03-ink">Journal</a>
<a href="#tsn-03-s2" class="inline-flex min-h-11 items-center rounded-full px-4 text-sm font-medium text-tsn03-mut hover:text-tsn03-ink focus-visible:outline-2 focus-visible:outline-tsn03-acc">Templates</a>
<a href="#tsn-03-s3" class="inline-flex min-h-11 items-center rounded-full px-4 text-sm font-medium text-tsn03-mut hover:text-tsn03-ink focus-visible:outline-2 focus-visible:outline-tsn03-acc">Sync</a>
<a href="#tsn-03-s4" class="inline-flex min-h-11 items-center rounded-full px-4 text-sm font-medium text-tsn03-mut hover:text-tsn03-ink focus-visible:outline-2 focus-visible:outline-tsn03-acc">Pricing</a>
</nav>
<a href="#tsn-03-s4" class="inline-flex min-h-11 items-center rounded-full bg-tsn03-acc px-5 text-sm font-bold text-tsn03-bg transition hover:brightness-110 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tsn03-acc max-md:ms-auto max-sm:hidden">Get the app</a>
<button type="button" data-tsn03-toggle aria-expanded="false" aria-controls="tsn-03-menu" class="relative grid size-11 place-items-center rounded-full border border-tsn03-line hover:bg-tsn03-ink/5 focus-visible:outline-2 focus-visible:outline-tsn03-acc max-sm:ms-auto md:hidden">
<span class="sr-only">Menu</span>
<span aria-hidden="true" class="absolute h-0.5 w-5 -translate-y-1.5 rounded-full bg-current transition-transform duration-300 group-data-open/hdr:translate-y-0 group-data-open/hdr:rotate-45 motion-reduce:transition-none"></span>
<span aria-hidden="true" class="absolute h-0.5 w-5 rounded-full bg-current transition-opacity group-data-open/hdr:opacity-0"></span>
<span aria-hidden="true" class="absolute h-0.5 w-5 translate-y-1.5 rounded-full bg-current transition-transform duration-300 group-data-open/hdr:translate-y-0 group-data-open/hdr:-rotate-45 motion-reduce:transition-none"></span>
</button>
</div>
<div id="tsn-03-menu" inert class="grid grid-rows-[0fr] transition-[grid-template-rows] duration-300 ease-out group-data-open/hdr:grid-rows-[1fr] motion-reduce:transition-none md:hidden">
<div class="min-h-0 overflow-hidden">
<nav aria-label="Mobile" class="grid gap-1 px-5 pb-6 pt-2">
<a href="#tsn-03-s1" class="flex min-h-14 items-center justify-between border-b border-tsn03-line text-2xl font-semibold tracking-tight opacity-0 transition-[opacity,translate] delay-75 duration-300 -translate-y-1 group-data-open/hdr:translate-y-0 group-data-open/hdr:opacity-100 focus-visible:outline-2 focus-visible:outline-tsn03-acc">Journal<span aria-hidden="true" class="text-tsn03-acc">01</span></a>
<a href="#tsn-03-s2" class="flex min-h-14 items-center justify-between border-b border-tsn03-line text-2xl font-semibold tracking-tight opacity-0 transition-[opacity,translate] delay-100 duration-300 -translate-y-1 group-data-open/hdr:translate-y-0 group-data-open/hdr:opacity-100 focus-visible:outline-2 focus-visible:outline-tsn03-acc">Templates<span aria-hidden="true" class="text-tsn03-acc">02</span></a>
<a href="#tsn-03-s3" class="flex min-h-14 items-center justify-between border-b border-tsn03-line text-2xl font-semibold tracking-tight opacity-0 transition-[opacity,translate] delay-150 duration-300 -translate-y-1 group-data-open/hdr:translate-y-0 group-data-open/hdr:opacity-100 focus-visible:outline-2 focus-visible:outline-tsn03-acc">Sync<span aria-hidden="true" class="text-tsn03-acc">03</span></a>
<a href="#tsn-03-s4" class="flex min-h-14 items-center justify-between border-b border-tsn03-line text-2xl font-semibold tracking-tight opacity-0 transition-[opacity,translate] delay-200 duration-300 -translate-y-1 group-data-open/hdr:translate-y-0 group-data-open/hdr:opacity-100 focus-visible:outline-2 focus-visible:outline-tsn03-acc">Pricing<span aria-hidden="true" class="text-tsn03-acc">04</span></a>
<a href="#tsn-03-s4" class="mt-4 inline-flex min-h-12 items-center justify-center rounded-full bg-tsn03-acc text-base font-bold text-tsn03-bg sm:hidden">Get the app</a>
</nav>
</div>
</div>
</header>
<div class="px-[max(1.25rem,calc((100%-72rem)/2))] pt-[clamp(3rem,8vw,6rem)] pb-[clamp(2rem,5vw,3.5rem)]">
<p class="font-tsn03-mono text-[11.5px] font-medium uppercase tracking-[0.16em] text-tsn03-acc">group-data-open · grid-rows-[1fr]</p>
<h1 class="mt-4 max-w-[18ch] text-[clamp(34px,5.6vw,64px)] font-semibold leading-[1.02] tracking-[-0.035em] text-balance">A hamburger that earns its place</h1>
<p class="mt-5 max-w-[54ch] text-[clamp(15px,1.6vw,17px)] leading-relaxed text-tsn03-mut text-pretty">Narrow the window below 768px and open the menu. It pushes down with a real height animation, staggers its links in, and closes on Escape.</p>
</div>
<main id="tsn-03-main" class="px-[max(1.25rem,calc((100%-72rem)/2))] grid gap-5 pb-24">
<section id="tsn-03-s1" class="scroll-mt-24 min-h-[44svh] rounded-[1.75rem] border border-tsn03-line bg-tsn03-surface p-[clamp(1.5rem,4vw,3rem)] shadow-[0_1px_2px_rgb(0_0_0/0.04)]" aria-labelledby="tsn-03-h1">
<p class="font-tsn03-mono text-[11px] font-medium uppercase tracking-[0.16em] text-tsn03-acc">Journal</p>
<h2 id="tsn-03-h1" class="mt-3 max-w-[22ch] text-[clamp(24px,3.2vw,36px)] font-semibold leading-[1.1] tracking-[-0.025em] text-balance">Height to auto, without measuring</h2>
<p class="mt-4 max-w-[58ch] text-base leading-[1.7] text-tsn03-mut text-pretty">The panel animates <code class="rounded-md bg-tsn03-acc/10 px-1.5 py-0.5 font-tsn03-mono text-[0.86em] text-tsn03-ink">grid-rows-[0fr]</code> to <code class="rounded-md bg-tsn03-acc/10 px-1.5 py-0.5 font-tsn03-mono text-[0.86em] text-tsn03-ink">[1fr]</code>. The browser interpolates the track; no <code class="rounded-md bg-tsn03-acc/10 px-1.5 py-0.5 font-tsn03-mono text-[0.86em] text-tsn03-ink">scrollHeight</code> reads, no magic max-height.</p>
</section>
<section id="tsn-03-s2" class="scroll-mt-24 min-h-[44svh] rounded-[1.75rem] border border-tsn03-line bg-tsn03-surface p-[clamp(1.5rem,4vw,3rem)] shadow-[0_1px_2px_rgb(0_0_0/0.04)]" aria-labelledby="tsn-03-h2">
<p class="font-tsn03-mono text-[11px] font-medium uppercase tracking-[0.16em] text-tsn03-acc">Templates</p>
<h2 id="tsn-03-h2" class="mt-3 max-w-[22ch] text-[clamp(24px,3.2vw,36px)] font-semibold leading-[1.1] tracking-[-0.025em] text-balance">One attribute, every state</h2>
<p class="mt-4 max-w-[58ch] text-base leading-[1.7] text-tsn03-mut text-pretty">The script toggles <code class="rounded-md bg-tsn03-acc/10 px-1.5 py-0.5 font-tsn03-mono text-[0.86em] text-tsn03-ink">data-open</code>. The X morph, the panel and the stagger are all <code class="rounded-md bg-tsn03-acc/10 px-1.5 py-0.5 font-tsn03-mono text-[0.86em] text-tsn03-ink">group-data-open/hdr:</code> variants in the markup.</p>
</section>
<section id="tsn-03-s3" class="scroll-mt-24 min-h-[44svh] rounded-[1.75rem] border border-tsn03-line bg-tsn03-surface p-[clamp(1.5rem,4vw,3rem)] shadow-[0_1px_2px_rgb(0_0_0/0.04)]" aria-labelledby="tsn-03-h3">
<p class="font-tsn03-mono text-[11px] font-medium uppercase tracking-[0.16em] text-tsn03-acc">Sync</p>
<h2 id="tsn-03-h3" class="mt-3 max-w-[22ch] text-[clamp(24px,3.2vw,36px)] font-semibold leading-[1.1] tracking-[-0.025em] text-balance">Closed means unreachable</h2>
<p class="mt-4 max-w-[58ch] text-base leading-[1.7] text-tsn03-mut text-pretty">A collapsed panel is <code class="rounded-md bg-tsn03-acc/10 px-1.5 py-0.5 font-tsn03-mono text-[0.86em] text-tsn03-ink">inert</code>, so its links leave the tab order and the accessibility tree until the menu opens.</p>
</section>
<section id="tsn-03-s4" class="scroll-mt-24 min-h-[44svh] rounded-[1.75rem] border border-tsn03-line bg-tsn03-surface p-[clamp(1.5rem,4vw,3rem)] shadow-[0_1px_2px_rgb(0_0_0/0.04)]" aria-labelledby="tsn-03-h4">
<p class="font-tsn03-mono text-[11px] font-medium uppercase tracking-[0.16em] text-tsn03-acc">Pricing</p>
<h2 id="tsn-03-h4" class="mt-3 max-w-[22ch] text-[clamp(24px,3.2vw,36px)] font-semibold leading-[1.1] tracking-[-0.025em] text-balance">Escape always works</h2>
<p class="mt-4 max-w-[58ch] text-base leading-[1.7] text-tsn03-mut text-pretty">Escape closes the menu and returns focus to the toggle. Tapping a link closes it too, so the anchor lands on an uncovered page.</p>
</section>
</main>
<footer class="border-t border-tsn03-line px-[max(1.25rem,calc((100%-72rem)/2))] py-10 font-tsn03-mono text-xs text-tsn03-mut">Tailwind sticky navigation · pattern 03 of 25</footer>
</div><div class="tsn-03 w-full min-h-svh bg-tsn03-bg font-tsn03-sans text-tsn03-ink antialiased" aria-label="Responsive Tailwind hamburger navbar demo">
<header data-tsn03-header class="group/hdr sticky top-0 z-40 border-b border-tsn03-line bg-tsn03-bg/85 backdrop-blur-xl">
<div class="flex h-16 items-center gap-6 px-[max(1.25rem,calc((100%-72rem)/2))]">
<a href="#tsn-03-s1" class="flex min-h-11 items-center gap-2 rounded-lg text-lg font-bold tracking-[-0.03em] focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-tsn03-acc"><span aria-hidden="true" class="size-3 rotate-45 rounded-[3px] bg-tsn03-acc"></span>Fieldnote</a>
<nav aria-label="Primary" class="ms-auto flex gap-1 max-md:hidden">
<a href="#tsn-03-s1" aria-current="page" class="inline-flex min-h-11 items-center rounded-full px-4 text-sm font-medium text-tsn03-mut hover:text-tsn03-ink focus-visible:outline-2 focus-visible:outline-tsn03-acc aria-[current=page]:bg-tsn03-ink/8 aria-[current=page]:text-tsn03-ink">Journal</a>
<a href="#tsn-03-s2" class="inline-flex min-h-11 items-center rounded-full px-4 text-sm font-medium text-tsn03-mut hover:text-tsn03-ink focus-visible:outline-2 focus-visible:outline-tsn03-acc">Templates</a>
<a href="#tsn-03-s3" class="inline-flex min-h-11 items-center rounded-full px-4 text-sm font-medium text-tsn03-mut hover:text-tsn03-ink focus-visible:outline-2 focus-visible:outline-tsn03-acc">Sync</a>
<a href="#tsn-03-s4" class="inline-flex min-h-11 items-center rounded-full px-4 text-sm font-medium text-tsn03-mut hover:text-tsn03-ink focus-visible:outline-2 focus-visible:outline-tsn03-acc">Pricing</a>
</nav>
<a href="#tsn-03-s4" class="inline-flex min-h-11 items-center rounded-full bg-tsn03-acc px-5 text-sm font-bold text-tsn03-bg transition hover:brightness-110 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tsn03-acc max-md:ms-auto max-sm:hidden">Get the app</a>
<button type="button" data-tsn03-toggle aria-expanded="false" aria-controls="tsn-03-menu" class="relative grid size-11 place-items-center rounded-full border border-tsn03-line hover:bg-tsn03-ink/5 focus-visible:outline-2 focus-visible:outline-tsn03-acc max-sm:ms-auto md:hidden">
<span class="sr-only">Menu</span>
<span aria-hidden="true" class="absolute h-0.5 w-5 -translate-y-1.5 rounded-full bg-current transition-transform duration-300 group-data-open/hdr:translate-y-0 group-data-open/hdr:rotate-45 motion-reduce:transition-none"></span>
<span aria-hidden="true" class="absolute h-0.5 w-5 rounded-full bg-current transition-opacity group-data-open/hdr:opacity-0"></span>
<span aria-hidden="true" class="absolute h-0.5 w-5 translate-y-1.5 rounded-full bg-current transition-transform duration-300 group-data-open/hdr:translate-y-0 group-data-open/hdr:-rotate-45 motion-reduce:transition-none"></span>
</button>
</div>
<div id="tsn-03-menu" inert class="grid grid-rows-[0fr] transition-[grid-template-rows] duration-300 ease-out group-data-open/hdr:grid-rows-[1fr] motion-reduce:transition-none md:hidden">
<div class="min-h-0 overflow-hidden">
<nav aria-label="Mobile" class="grid gap-1 px-5 pb-6 pt-2">
<a href="#tsn-03-s1" class="flex min-h-14 items-center justify-between border-b border-tsn03-line text-2xl font-semibold tracking-tight opacity-0 transition-[opacity,translate] delay-75 duration-300 -translate-y-1 group-data-open/hdr:translate-y-0 group-data-open/hdr:opacity-100 focus-visible:outline-2 focus-visible:outline-tsn03-acc">Journal<span aria-hidden="true" class="text-tsn03-acc">01</span></a>
<a href="#tsn-03-s2" class="flex min-h-14 items-center justify-between border-b border-tsn03-line text-2xl font-semibold tracking-tight opacity-0 transition-[opacity,translate] delay-100 duration-300 -translate-y-1 group-data-open/hdr:translate-y-0 group-data-open/hdr:opacity-100 focus-visible:outline-2 focus-visible:outline-tsn03-acc">Templates<span aria-hidden="true" class="text-tsn03-acc">02</span></a>
<a href="#tsn-03-s3" class="flex min-h-14 items-center justify-between border-b border-tsn03-line text-2xl font-semibold tracking-tight opacity-0 transition-[opacity,translate] delay-150 duration-300 -translate-y-1 group-data-open/hdr:translate-y-0 group-data-open/hdr:opacity-100 focus-visible:outline-2 focus-visible:outline-tsn03-acc">Sync<span aria-hidden="true" class="text-tsn03-acc">03</span></a>
<a href="#tsn-03-s4" class="flex min-h-14 items-center justify-between border-b border-tsn03-line text-2xl font-semibold tracking-tight opacity-0 transition-[opacity,translate] delay-200 duration-300 -translate-y-1 group-data-open/hdr:translate-y-0 group-data-open/hdr:opacity-100 focus-visible:outline-2 focus-visible:outline-tsn03-acc">Pricing<span aria-hidden="true" class="text-tsn03-acc">04</span></a>
<a href="#tsn-03-s4" class="mt-4 inline-flex min-h-12 items-center justify-center rounded-full bg-tsn03-acc text-base font-bold text-tsn03-bg sm:hidden">Get the app</a>
</nav>
</div>
</div>
</header>
<div class="px-[max(1.25rem,calc((100%-72rem)/2))] pt-[clamp(3rem,8vw,6rem)] pb-[clamp(2rem,5vw,3.5rem)]">
<p class="font-tsn03-mono text-[11.5px] font-medium uppercase tracking-[0.16em] text-tsn03-acc">group-data-open · grid-rows-[1fr]</p>
<h1 class="mt-4 max-w-[18ch] text-[clamp(34px,5.6vw,64px)] font-semibold leading-[1.02] tracking-[-0.035em] text-balance">A hamburger that earns its place</h1>
<p class="mt-5 max-w-[54ch] text-[clamp(15px,1.6vw,17px)] leading-relaxed text-tsn03-mut text-pretty">Narrow the window below 768px and open the menu. It pushes down with a real height animation, staggers its links in, and closes on Escape.</p>
</div>
<main id="tsn-03-main" class="px-[max(1.25rem,calc((100%-72rem)/2))] grid gap-5 pb-24">
<section id="tsn-03-s1" class="scroll-mt-24 min-h-[44svh] rounded-[1.75rem] border border-tsn03-line bg-tsn03-surface p-[clamp(1.5rem,4vw,3rem)] shadow-[0_1px_2px_rgb(0_0_0/0.04)]" aria-labelledby="tsn-03-h1">
<p class="font-tsn03-mono text-[11px] font-medium uppercase tracking-[0.16em] text-tsn03-acc">Journal</p>
<h2 id="tsn-03-h1" class="mt-3 max-w-[22ch] text-[clamp(24px,3.2vw,36px)] font-semibold leading-[1.1] tracking-[-0.025em] text-balance">Height to auto, without measuring</h2>
<p class="mt-4 max-w-[58ch] text-base leading-[1.7] text-tsn03-mut text-pretty">The panel animates <code class="rounded-md bg-tsn03-acc/10 px-1.5 py-0.5 font-tsn03-mono text-[0.86em] text-tsn03-ink">grid-rows-[0fr]</code> to <code class="rounded-md bg-tsn03-acc/10 px-1.5 py-0.5 font-tsn03-mono text-[0.86em] text-tsn03-ink">[1fr]</code>. The browser interpolates the track; no <code class="rounded-md bg-tsn03-acc/10 px-1.5 py-0.5 font-tsn03-mono text-[0.86em] text-tsn03-ink">scrollHeight</code> reads, no magic max-height.</p>
</section>
<section id="tsn-03-s2" class="scroll-mt-24 min-h-[44svh] rounded-[1.75rem] border border-tsn03-line bg-tsn03-surface p-[clamp(1.5rem,4vw,3rem)] shadow-[0_1px_2px_rgb(0_0_0/0.04)]" aria-labelledby="tsn-03-h2">
<p class="font-tsn03-mono text-[11px] font-medium uppercase tracking-[0.16em] text-tsn03-acc">Templates</p>
<h2 id="tsn-03-h2" class="mt-3 max-w-[22ch] text-[clamp(24px,3.2vw,36px)] font-semibold leading-[1.1] tracking-[-0.025em] text-balance">One attribute, every state</h2>
<p class="mt-4 max-w-[58ch] text-base leading-[1.7] text-tsn03-mut text-pretty">The script toggles <code class="rounded-md bg-tsn03-acc/10 px-1.5 py-0.5 font-tsn03-mono text-[0.86em] text-tsn03-ink">data-open</code>. The X morph, the panel and the stagger are all <code class="rounded-md bg-tsn03-acc/10 px-1.5 py-0.5 font-tsn03-mono text-[0.86em] text-tsn03-ink">group-data-open/hdr:</code> variants in the markup.</p>
</section>
<section id="tsn-03-s3" class="scroll-mt-24 min-h-[44svh] rounded-[1.75rem] border border-tsn03-line bg-tsn03-surface p-[clamp(1.5rem,4vw,3rem)] shadow-[0_1px_2px_rgb(0_0_0/0.04)]" aria-labelledby="tsn-03-h3">
<p class="font-tsn03-mono text-[11px] font-medium uppercase tracking-[0.16em] text-tsn03-acc">Sync</p>
<h2 id="tsn-03-h3" class="mt-3 max-w-[22ch] text-[clamp(24px,3.2vw,36px)] font-semibold leading-[1.1] tracking-[-0.025em] text-balance">Closed means unreachable</h2>
<p class="mt-4 max-w-[58ch] text-base leading-[1.7] text-tsn03-mut text-pretty">A collapsed panel is <code class="rounded-md bg-tsn03-acc/10 px-1.5 py-0.5 font-tsn03-mono text-[0.86em] text-tsn03-ink">inert</code>, so its links leave the tab order and the accessibility tree until the menu opens.</p>
</section>
<section id="tsn-03-s4" class="scroll-mt-24 min-h-[44svh] rounded-[1.75rem] border border-tsn03-line bg-tsn03-surface p-[clamp(1.5rem,4vw,3rem)] shadow-[0_1px_2px_rgb(0_0_0/0.04)]" aria-labelledby="tsn-03-h4">
<p class="font-tsn03-mono text-[11px] font-medium uppercase tracking-[0.16em] text-tsn03-acc">Pricing</p>
<h2 id="tsn-03-h4" class="mt-3 max-w-[22ch] text-[clamp(24px,3.2vw,36px)] font-semibold leading-[1.1] tracking-[-0.025em] text-balance">Escape always works</h2>
<p class="mt-4 max-w-[58ch] text-base leading-[1.7] text-tsn03-mut text-pretty">Escape closes the menu and returns focus to the toggle. Tapping a link closes it too, so the anchor lands on an uncovered page.</p>
</section>
</main>
<footer class="border-t border-tsn03-line px-[max(1.25rem,calc((100%-72rem)/2))] py-10 font-tsn03-mono text-xs text-tsn03-mut">Tailwind sticky navigation · pattern 03 of 25</footer>
</div>@import "tailwindcss";
@theme {
--color-tsn03-bg: #0f1110;
--color-tsn03-bg: oklch(0.17 0.006 150);
--color-tsn03-surface: #171a18;
--color-tsn03-surface: oklch(0.21 0.008 150);
--color-tsn03-ink: #eef2ea;
--color-tsn03-ink: oklch(0.95 0.012 130);
--color-tsn03-mut: #a3aa9d;
--color-tsn03-mut: oklch(0.72 0.018 130);
--color-tsn03-acc: #c6f24e;
--color-tsn03-acc: oklch(0.9 0.19 125);
--color-tsn03-line: #eef2ea1f;
--color-tsn03-line: oklch(0.95 0.012 130 / 0.12);
--font-tsn03-sans: 'Space Grotesk', system-ui, sans-serif;
--font-tsn03-mono: 'JetBrains Mono', ui-monospace, monospace;
}
/* Root rule: the full-bleed contract, restated in CSS so the page is sized before the browser build compiles. */
.tsn-03 {
width: 100%;
min-height: 100svh;
}
/* Smooth anchor jumps live on <html>, outside the markup, so they stay in CSS. */
@media (prefers-reduced-motion: no-preference) {
:root:has(.tsn-03) {
scroll-behavior: smooth;
}
}@import "tailwindcss";
@theme {
--color-tsn03-bg: #0f1110;
--color-tsn03-bg: oklch(0.17 0.006 150);
--color-tsn03-surface: #171a18;
--color-tsn03-surface: oklch(0.21 0.008 150);
--color-tsn03-ink: #eef2ea;
--color-tsn03-ink: oklch(0.95 0.012 130);
--color-tsn03-mut: #a3aa9d;
--color-tsn03-mut: oklch(0.72 0.018 130);
--color-tsn03-acc: #c6f24e;
--color-tsn03-acc: oklch(0.9 0.19 125);
--color-tsn03-line: #eef2ea1f;
--color-tsn03-line: oklch(0.95 0.012 130 / 0.12);
--font-tsn03-sans: 'Space Grotesk', system-ui, sans-serif;
--font-tsn03-mono: 'JetBrains Mono', ui-monospace, monospace;
}
/* Root rule: the full-bleed contract, restated in CSS so the page is sized before the browser build compiles. */
.tsn-03 {
width: 100%;
min-height: 100svh;
}
/* Smooth anchor jumps live on <html>, outside the markup, so they stay in CSS. */
@media (prefers-reduced-motion: no-preference) {
:root:has(.tsn-03) {
scroll-behavior: smooth;
}
}(() => {
document.querySelectorAll('.tsn-03').forEach((root) => {
if (root.dataset.wired) return;
root.dataset.wired = '1';
const hdr = root.querySelector('[data-tsn03-header]');
const btn = root.querySelector('[data-tsn03-toggle]');
const panel = root.querySelector('#tsn-03-menu');
const set = (open) => {
hdr.toggleAttribute('data-open', open);
btn.setAttribute('aria-expanded', String(open));
panel.inert = !open;
};
btn.addEventListener('click', () => set(!hdr.hasAttribute('data-open')));
panel.addEventListener('click', (e) => { if (e.target.closest('a')) set(false); });
root.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && hdr.hasAttribute('data-open')) { set(false); btn.focus(); }
});
matchMedia('(min-width: 768px)').addEventListener('change', (e) => { if (e.matches) set(false); });
});
})();(() => {
document.querySelectorAll('.tsn-03').forEach((root) => {
if (root.dataset.wired) return;
root.dataset.wired = '1';
const hdr = root.querySelector('[data-tsn03-header]');
const btn = root.querySelector('[data-tsn03-toggle]');
const panel = root.querySelector('#tsn-03-menu');
const set = (open) => {
hdr.toggleAttribute('data-open', open);
btn.setAttribute('aria-expanded', String(open));
panel.inert = !open;
};
btn.addEventListener('click', () => set(!hdr.hasAttribute('data-open')));
panel.addEventListener('click', (e) => { if (e.target.closest('a')) set(false); });
root.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && hdr.hasAttribute('data-open')) { set(false); btn.focus(); }
});
matchMedia('(min-width: 768px)').addEventListener('change', (e) => { if (e.matches) set(false); });
});
})();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-tsn03-bg: #0f1110;
--color-tsn03-bg: oklch(0.17 0.006 150);
--color-tsn03-surface: #171a18;
--color-tsn03-surface: oklch(0.21 0.008 150);
--color-tsn03-ink: #eef2ea;
--color-tsn03-ink: oklch(0.95 0.012 130);
--color-tsn03-mut: #a3aa9d;
--color-tsn03-mut: oklch(0.72 0.018 130);
--color-tsn03-acc: #c6f24e;
--color-tsn03-acc: oklch(0.9 0.19 125);
--color-tsn03-line: #eef2ea1f;
--color-tsn03-line: oklch(0.95 0.012 130 / 0.12);
--font-tsn03-sans: 'Space Grotesk', system-ui, sans-serif;
--font-tsn03-mono: 'JetBrains Mono', ui-monospace, monospace;
}
/* Root rule: the full-bleed contract, restated in CSS so the page is sized before the browser build compiles. */
.tsn-03 {
width: 100%;
min-height: 100svh;
}
/* Smooth anchor jumps live on <html>, outside the markup, so they stay in CSS. */
@media (prefers-reduced-motion: no-preference) {
:root:has(.tsn-03) {
scroll-behavior: smooth;
}
}@import "tailwindcss";
@theme {
--color-tsn03-bg: #0f1110;
--color-tsn03-bg: oklch(0.17 0.006 150);
--color-tsn03-surface: #171a18;
--color-tsn03-surface: oklch(0.21 0.008 150);
--color-tsn03-ink: #eef2ea;
--color-tsn03-ink: oklch(0.95 0.012 130);
--color-tsn03-mut: #a3aa9d;
--color-tsn03-mut: oklch(0.72 0.018 130);
--color-tsn03-acc: #c6f24e;
--color-tsn03-acc: oklch(0.9 0.19 125);
--color-tsn03-line: #eef2ea1f;
--color-tsn03-line: oklch(0.95 0.012 130 / 0.12);
--font-tsn03-sans: 'Space Grotesk', system-ui, sans-serif;
--font-tsn03-mono: 'JetBrains Mono', ui-monospace, monospace;
}
/* Root rule: the full-bleed contract, restated in CSS so the page is sized before the browser build compiles. */
.tsn-03 {
width: 100%;
min-height: 100svh;
}
/* Smooth anchor jumps live on <html>, outside the markup, so they stay in CSS. */
@media (prefers-reduced-motion: no-preference) {
:root:has(.tsn-03) {
scroll-behavior: smooth;
}
}<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&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&family=JetBrains+Mono:wght@400;500;600&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
.is-openand its five descendant rules became adata-openattribute read bygroup-data-open/hdr:variants — the script no longer knows any class names.- UI upgrade: the CSS original toggled
display; the panel now pushes down with a true auto-height animation and staggers its links withdelay-75…delay-200. - UI upgrade: the closed panel is
inert, Escape returns focus to the toggle, and crossing the breakpoint resets the state so desktop never inherits an open menu.
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: Responsive Tailwind Sticky Navbar with Hamburger Menu
Source: https://codefronts.com/navigation/tailwind-css-sticky-navigations/responsive-tailwind-sticky-navbar-hamburger-menu/
The responsive header with a hamburger that morphs into an X and a menu that opens by animating grid-rows-[0fr] to grid-rows-[1fr] — true height animation without measuring. A nine-line script sets one data-open attribute; every visual state is a group-data-open/hdr: variant. Escape closes, focus returns, and the closed panel is inert.
## HTML
```html
<div class="tsn-03 w-full min-h-svh bg-tsn03-bg font-tsn03-sans text-tsn03-ink antialiased" aria-label="Responsive Tailwind hamburger navbar demo">
<header data-tsn03-header class="group/hdr sticky top-0 z-40 border-b border-tsn03-line bg-tsn03-bg/85 backdrop-blur-xl">
<div class="flex h-16 items-center gap-6 px-[max(1.25rem,calc((100%-72rem)/2))]">
<a href="#tsn-03-s1" class="flex min-h-11 items-center gap-2 rounded-lg text-lg font-bold tracking-[-0.03em] focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-tsn03-acc"><span aria-hidden="true" class="size-3 rotate-45 rounded-[3px] bg-tsn03-acc"></span>Fieldnote</a>
<nav aria-label="Primary" class="ms-auto flex gap-1 max-md:hidden">
<a href="#tsn-03-s1" aria-current="page" class="inline-flex min-h-11 items-center rounded-full px-4 text-sm font-medium text-tsn03-mut hover:text-tsn03-ink focus-visible:outline-2 focus-visible:outline-tsn03-acc aria-[current=page]:bg-tsn03-ink/8 aria-[current=page]:text-tsn03-ink">Journal</a>
<a href="#tsn-03-s2" class="inline-flex min-h-11 items-center rounded-full px-4 text-sm font-medium text-tsn03-mut hover:text-tsn03-ink focus-visible:outline-2 focus-visible:outline-tsn03-acc">Templates</a>
<a href="#tsn-03-s3" class="inline-flex min-h-11 items-center rounded-full px-4 text-sm font-medium text-tsn03-mut hover:text-tsn03-ink focus-visible:outline-2 focus-visible:outline-tsn03-acc">Sync</a>
<a href="#tsn-03-s4" class="inline-flex min-h-11 items-center rounded-full px-4 text-sm font-medium text-tsn03-mut hover:text-tsn03-ink focus-visible:outline-2 focus-visible:outline-tsn03-acc">Pricing</a>
</nav>
<a href="#tsn-03-s4" class="inline-flex min-h-11 items-center rounded-full bg-tsn03-acc px-5 text-sm font-bold text-tsn03-bg transition hover:brightness-110 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tsn03-acc max-md:ms-auto max-sm:hidden">Get the app</a>
<button type="button" data-tsn03-toggle aria-expanded="false" aria-controls="tsn-03-menu" class="relative grid size-11 place-items-center rounded-full border border-tsn03-line hover:bg-tsn03-ink/5 focus-visible:outline-2 focus-visible:outline-tsn03-acc max-sm:ms-auto md:hidden">
<span class="sr-only">Menu</span>
<span aria-hidden="true" class="absolute h-0.5 w-5 -translate-y-1.5 rounded-full bg-current transition-transform duration-300 group-data-open/hdr:translate-y-0 group-data-open/hdr:rotate-45 motion-reduce:transition-none"></span>
<span aria-hidden="true" class="absolute h-0.5 w-5 rounded-full bg-current transition-opacity group-data-open/hdr:opacity-0"></span>
<span aria-hidden="true" class="absolute h-0.5 w-5 translate-y-1.5 rounded-full bg-current transition-transform duration-300 group-data-open/hdr:translate-y-0 group-data-open/hdr:-rotate-45 motion-reduce:transition-none"></span>
</button>
</div>
<div id="tsn-03-menu" inert class="grid grid-rows-[0fr] transition-[grid-template-rows] duration-300 ease-out group-data-open/hdr:grid-rows-[1fr] motion-reduce:transition-none md:hidden">
<div class="min-h-0 overflow-hidden">
<nav aria-label="Mobile" class="grid gap-1 px-5 pb-6 pt-2">
<a href="#tsn-03-s1" class="flex min-h-14 items-center justify-between border-b border-tsn03-line text-2xl font-semibold tracking-tight opacity-0 transition-[opacity,translate] delay-75 duration-300 -translate-y-1 group-data-open/hdr:translate-y-0 group-data-open/hdr:opacity-100 focus-visible:outline-2 focus-visible:outline-tsn03-acc">Journal<span aria-hidden="true" class="text-tsn03-acc">01</span></a>
<a href="#tsn-03-s2" class="flex min-h-14 items-center justify-between border-b border-tsn03-line text-2xl font-semibold tracking-tight opacity-0 transition-[opacity,translate] delay-100 duration-300 -translate-y-1 group-data-open/hdr:translate-y-0 group-data-open/hdr:opacity-100 focus-visible:outline-2 focus-visible:outline-tsn03-acc">Templates<span aria-hidden="true" class="text-tsn03-acc">02</span></a>
<a href="#tsn-03-s3" class="flex min-h-14 items-center justify-between border-b border-tsn03-line text-2xl font-semibold tracking-tight opacity-0 transition-[opacity,translate] delay-150 duration-300 -translate-y-1 group-data-open/hdr:translate-y-0 group-data-open/hdr:opacity-100 focus-visible:outline-2 focus-visible:outline-tsn03-acc">Sync<span aria-hidden="true" class="text-tsn03-acc">03</span></a>
<a href="#tsn-03-s4" class="flex min-h-14 items-center justify-between border-b border-tsn03-line text-2xl font-semibold tracking-tight opacity-0 transition-[opacity,translate] delay-200 duration-300 -translate-y-1 group-data-open/hdr:translate-y-0 group-data-open/hdr:opacity-100 focus-visible:outline-2 focus-visible:outline-tsn03-acc">Pricing<span aria-hidden="true" class="text-tsn03-acc">04</span></a>
<a href="#tsn-03-s4" class="mt-4 inline-flex min-h-12 items-center justify-center rounded-full bg-tsn03-acc text-base font-bold text-tsn03-bg sm:hidden">Get the app</a>
</nav>
</div>
</div>
</header>
<div class="px-[max(1.25rem,calc((100%-72rem)/2))] pt-[clamp(3rem,8vw,6rem)] pb-[clamp(2rem,5vw,3.5rem)]">
<p class="font-tsn03-mono text-[11.5px] font-medium uppercase tracking-[0.16em] text-tsn03-acc">group-data-open · grid-rows-[1fr]</p>
<h1 class="mt-4 max-w-[18ch] text-[clamp(34px,5.6vw,64px)] font-semibold leading-[1.02] tracking-[-0.035em] text-balance">A hamburger that earns its place</h1>
<p class="mt-5 max-w-[54ch] text-[clamp(15px,1.6vw,17px)] leading-relaxed text-tsn03-mut text-pretty">Narrow the window below 768px and open the menu. It pushes down with a real height animation, staggers its links in, and closes on Escape.</p>
</div>
<main id="tsn-03-main" class="px-[max(1.25rem,calc((100%-72rem)/2))] grid gap-5 pb-24">
<section id="tsn-03-s1" class="scroll-mt-24 min-h-[44svh] rounded-[1.75rem] border border-tsn03-line bg-tsn03-surface p-[clamp(1.5rem,4vw,3rem)] shadow-[0_1px_2px_rgb(0_0_0/0.04)]" aria-labelledby="tsn-03-h1">
<p class="font-tsn03-mono text-[11px] font-medium uppercase tracking-[0.16em] text-tsn03-acc">Journal</p>
<h2 id="tsn-03-h1" class="mt-3 max-w-[22ch] text-[clamp(24px,3.2vw,36px)] font-semibold leading-[1.1] tracking-[-0.025em] text-balance">Height to auto, without measuring</h2>
<p class="mt-4 max-w-[58ch] text-base leading-[1.7] text-tsn03-mut text-pretty">The panel animates <code class="rounded-md bg-tsn03-acc/10 px-1.5 py-0.5 font-tsn03-mono text-[0.86em] text-tsn03-ink">grid-rows-[0fr]</code> to <code class="rounded-md bg-tsn03-acc/10 px-1.5 py-0.5 font-tsn03-mono text-[0.86em] text-tsn03-ink">[1fr]</code>. The browser interpolates the track; no <code class="rounded-md bg-tsn03-acc/10 px-1.5 py-0.5 font-tsn03-mono text-[0.86em] text-tsn03-ink">scrollHeight</code> reads, no magic max-height.</p>
</section>
<section id="tsn-03-s2" class="scroll-mt-24 min-h-[44svh] rounded-[1.75rem] border border-tsn03-line bg-tsn03-surface p-[clamp(1.5rem,4vw,3rem)] shadow-[0_1px_2px_rgb(0_0_0/0.04)]" aria-labelledby="tsn-03-h2">
<p class="font-tsn03-mono text-[11px] font-medium uppercase tracking-[0.16em] text-tsn03-acc">Templates</p>
<h2 id="tsn-03-h2" class="mt-3 max-w-[22ch] text-[clamp(24px,3.2vw,36px)] font-semibold leading-[1.1] tracking-[-0.025em] text-balance">One attribute, every state</h2>
<p class="mt-4 max-w-[58ch] text-base leading-[1.7] text-tsn03-mut text-pretty">The script toggles <code class="rounded-md bg-tsn03-acc/10 px-1.5 py-0.5 font-tsn03-mono text-[0.86em] text-tsn03-ink">data-open</code>. The X morph, the panel and the stagger are all <code class="rounded-md bg-tsn03-acc/10 px-1.5 py-0.5 font-tsn03-mono text-[0.86em] text-tsn03-ink">group-data-open/hdr:</code> variants in the markup.</p>
</section>
<section id="tsn-03-s3" class="scroll-mt-24 min-h-[44svh] rounded-[1.75rem] border border-tsn03-line bg-tsn03-surface p-[clamp(1.5rem,4vw,3rem)] shadow-[0_1px_2px_rgb(0_0_0/0.04)]" aria-labelledby="tsn-03-h3">
<p class="font-tsn03-mono text-[11px] font-medium uppercase tracking-[0.16em] text-tsn03-acc">Sync</p>
<h2 id="tsn-03-h3" class="mt-3 max-w-[22ch] text-[clamp(24px,3.2vw,36px)] font-semibold leading-[1.1] tracking-[-0.025em] text-balance">Closed means unreachable</h2>
<p class="mt-4 max-w-[58ch] text-base leading-[1.7] text-tsn03-mut text-pretty">A collapsed panel is <code class="rounded-md bg-tsn03-acc/10 px-1.5 py-0.5 font-tsn03-mono text-[0.86em] text-tsn03-ink">inert</code>, so its links leave the tab order and the accessibility tree until the menu opens.</p>
</section>
<section id="tsn-03-s4" class="scroll-mt-24 min-h-[44svh] rounded-[1.75rem] border border-tsn03-line bg-tsn03-surface p-[clamp(1.5rem,4vw,3rem)] shadow-[0_1px_2px_rgb(0_0_0/0.04)]" aria-labelledby="tsn-03-h4">
<p class="font-tsn03-mono text-[11px] font-medium uppercase tracking-[0.16em] text-tsn03-acc">Pricing</p>
<h2 id="tsn-03-h4" class="mt-3 max-w-[22ch] text-[clamp(24px,3.2vw,36px)] font-semibold leading-[1.1] tracking-[-0.025em] text-balance">Escape always works</h2>
<p class="mt-4 max-w-[58ch] text-base leading-[1.7] text-tsn03-mut text-pretty">Escape closes the menu and returns focus to the toggle. Tapping a link closes it too, so the anchor lands on an uncovered page.</p>
</section>
</main>
<footer class="border-t border-tsn03-line px-[max(1.25rem,calc((100%-72rem)/2))] py-10 font-tsn03-mono text-xs text-tsn03-mut">Tailwind sticky navigation · pattern 03 of 25</footer>
</div>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tsn03-bg: #0f1110;
--color-tsn03-bg: oklch(0.17 0.006 150);
--color-tsn03-surface: #171a18;
--color-tsn03-surface: oklch(0.21 0.008 150);
--color-tsn03-ink: #eef2ea;
--color-tsn03-ink: oklch(0.95 0.012 130);
--color-tsn03-mut: #a3aa9d;
--color-tsn03-mut: oklch(0.72 0.018 130);
--color-tsn03-acc: #c6f24e;
--color-tsn03-acc: oklch(0.9 0.19 125);
--color-tsn03-line: #eef2ea1f;
--color-tsn03-line: oklch(0.95 0.012 130 / 0.12);
--font-tsn03-sans: 'Space Grotesk', system-ui, sans-serif;
--font-tsn03-mono: 'JetBrains Mono', ui-monospace, monospace;
}
/* Root rule: the full-bleed contract, restated in CSS so the page is sized before the browser build compiles. */
.tsn-03 {
width: 100%;
min-height: 100svh;
}
/* Smooth anchor jumps live on <html>, outside the markup, so they stay in CSS. */
@media (prefers-reduced-motion: no-preference) {
:root:has(.tsn-03) {
scroll-behavior: smooth;
}
}
```
## JavaScript
```js
(() => {
document.querySelectorAll('.tsn-03').forEach((root) => {
if (root.dataset.wired) return;
root.dataset.wired = '1';
const hdr = root.querySelector('[data-tsn03-header]');
const btn = root.querySelector('[data-tsn03-toggle]');
const panel = root.querySelector('#tsn-03-menu');
const set = (open) => {
hdr.toggleAttribute('data-open', open);
btn.setAttribute('aria-expanded', String(open));
panel.inert = !open;
};
btn.addEventListener('click', () => set(!hdr.hasAttribute('data-open')));
panel.addEventListener('click', (e) => { if (e.target.closest('a')) set(false); });
root.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && hdr.hasAttribute('data-open')) { set(false); btn.focus(); }
});
matchMedia('(min-width: 768px)').addEventListener('change', (e) => { if (e.matches) set(false); });
});
})();
```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: Responsive Tailwind Sticky Navbar with Hamburger Menu
Source: https://codefronts.com/navigation/tailwind-css-sticky-navigations/responsive-tailwind-sticky-navbar-hamburger-menu/
The responsive header with a hamburger that morphs into an X and a menu that opens by animating grid-rows-[0fr] to grid-rows-[1fr] — true height animation without measuring. A nine-line script sets one data-open attribute; every visual state is a group-data-open/hdr: variant. Escape closes, focus returns, and the closed panel is inert.
## HTML
```html
<div class="tsn-03 w-full min-h-svh bg-tsn03-bg font-tsn03-sans text-tsn03-ink antialiased" aria-label="Responsive Tailwind hamburger navbar demo">
<header data-tsn03-header class="group/hdr sticky top-0 z-40 border-b border-tsn03-line bg-tsn03-bg/85 backdrop-blur-xl">
<div class="flex h-16 items-center gap-6 px-[max(1.25rem,calc((100%-72rem)/2))]">
<a href="#tsn-03-s1" class="flex min-h-11 items-center gap-2 rounded-lg text-lg font-bold tracking-[-0.03em] focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-tsn03-acc"><span aria-hidden="true" class="size-3 rotate-45 rounded-[3px] bg-tsn03-acc"></span>Fieldnote</a>
<nav aria-label="Primary" class="ms-auto flex gap-1 max-md:hidden">
<a href="#tsn-03-s1" aria-current="page" class="inline-flex min-h-11 items-center rounded-full px-4 text-sm font-medium text-tsn03-mut hover:text-tsn03-ink focus-visible:outline-2 focus-visible:outline-tsn03-acc aria-[current=page]:bg-tsn03-ink/8 aria-[current=page]:text-tsn03-ink">Journal</a>
<a href="#tsn-03-s2" class="inline-flex min-h-11 items-center rounded-full px-4 text-sm font-medium text-tsn03-mut hover:text-tsn03-ink focus-visible:outline-2 focus-visible:outline-tsn03-acc">Templates</a>
<a href="#tsn-03-s3" class="inline-flex min-h-11 items-center rounded-full px-4 text-sm font-medium text-tsn03-mut hover:text-tsn03-ink focus-visible:outline-2 focus-visible:outline-tsn03-acc">Sync</a>
<a href="#tsn-03-s4" class="inline-flex min-h-11 items-center rounded-full px-4 text-sm font-medium text-tsn03-mut hover:text-tsn03-ink focus-visible:outline-2 focus-visible:outline-tsn03-acc">Pricing</a>
</nav>
<a href="#tsn-03-s4" class="inline-flex min-h-11 items-center rounded-full bg-tsn03-acc px-5 text-sm font-bold text-tsn03-bg transition hover:brightness-110 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tsn03-acc max-md:ms-auto max-sm:hidden">Get the app</a>
<button type="button" data-tsn03-toggle aria-expanded="false" aria-controls="tsn-03-menu" class="relative grid size-11 place-items-center rounded-full border border-tsn03-line hover:bg-tsn03-ink/5 focus-visible:outline-2 focus-visible:outline-tsn03-acc max-sm:ms-auto md:hidden">
<span class="sr-only">Menu</span>
<span aria-hidden="true" class="absolute h-0.5 w-5 -translate-y-1.5 rounded-full bg-current transition-transform duration-300 group-data-open/hdr:translate-y-0 group-data-open/hdr:rotate-45 motion-reduce:transition-none"></span>
<span aria-hidden="true" class="absolute h-0.5 w-5 rounded-full bg-current transition-opacity group-data-open/hdr:opacity-0"></span>
<span aria-hidden="true" class="absolute h-0.5 w-5 translate-y-1.5 rounded-full bg-current transition-transform duration-300 group-data-open/hdr:translate-y-0 group-data-open/hdr:-rotate-45 motion-reduce:transition-none"></span>
</button>
</div>
<div id="tsn-03-menu" inert class="grid grid-rows-[0fr] transition-[grid-template-rows] duration-300 ease-out group-data-open/hdr:grid-rows-[1fr] motion-reduce:transition-none md:hidden">
<div class="min-h-0 overflow-hidden">
<nav aria-label="Mobile" class="grid gap-1 px-5 pb-6 pt-2">
<a href="#tsn-03-s1" class="flex min-h-14 items-center justify-between border-b border-tsn03-line text-2xl font-semibold tracking-tight opacity-0 transition-[opacity,translate] delay-75 duration-300 -translate-y-1 group-data-open/hdr:translate-y-0 group-data-open/hdr:opacity-100 focus-visible:outline-2 focus-visible:outline-tsn03-acc">Journal<span aria-hidden="true" class="text-tsn03-acc">01</span></a>
<a href="#tsn-03-s2" class="flex min-h-14 items-center justify-between border-b border-tsn03-line text-2xl font-semibold tracking-tight opacity-0 transition-[opacity,translate] delay-100 duration-300 -translate-y-1 group-data-open/hdr:translate-y-0 group-data-open/hdr:opacity-100 focus-visible:outline-2 focus-visible:outline-tsn03-acc">Templates<span aria-hidden="true" class="text-tsn03-acc">02</span></a>
<a href="#tsn-03-s3" class="flex min-h-14 items-center justify-between border-b border-tsn03-line text-2xl font-semibold tracking-tight opacity-0 transition-[opacity,translate] delay-150 duration-300 -translate-y-1 group-data-open/hdr:translate-y-0 group-data-open/hdr:opacity-100 focus-visible:outline-2 focus-visible:outline-tsn03-acc">Sync<span aria-hidden="true" class="text-tsn03-acc">03</span></a>
<a href="#tsn-03-s4" class="flex min-h-14 items-center justify-between border-b border-tsn03-line text-2xl font-semibold tracking-tight opacity-0 transition-[opacity,translate] delay-200 duration-300 -translate-y-1 group-data-open/hdr:translate-y-0 group-data-open/hdr:opacity-100 focus-visible:outline-2 focus-visible:outline-tsn03-acc">Pricing<span aria-hidden="true" class="text-tsn03-acc">04</span></a>
<a href="#tsn-03-s4" class="mt-4 inline-flex min-h-12 items-center justify-center rounded-full bg-tsn03-acc text-base font-bold text-tsn03-bg sm:hidden">Get the app</a>
</nav>
</div>
</div>
</header>
<div class="px-[max(1.25rem,calc((100%-72rem)/2))] pt-[clamp(3rem,8vw,6rem)] pb-[clamp(2rem,5vw,3.5rem)]">
<p class="font-tsn03-mono text-[11.5px] font-medium uppercase tracking-[0.16em] text-tsn03-acc">group-data-open · grid-rows-[1fr]</p>
<h1 class="mt-4 max-w-[18ch] text-[clamp(34px,5.6vw,64px)] font-semibold leading-[1.02] tracking-[-0.035em] text-balance">A hamburger that earns its place</h1>
<p class="mt-5 max-w-[54ch] text-[clamp(15px,1.6vw,17px)] leading-relaxed text-tsn03-mut text-pretty">Narrow the window below 768px and open the menu. It pushes down with a real height animation, staggers its links in, and closes on Escape.</p>
</div>
<main id="tsn-03-main" class="px-[max(1.25rem,calc((100%-72rem)/2))] grid gap-5 pb-24">
<section id="tsn-03-s1" class="scroll-mt-24 min-h-[44svh] rounded-[1.75rem] border border-tsn03-line bg-tsn03-surface p-[clamp(1.5rem,4vw,3rem)] shadow-[0_1px_2px_rgb(0_0_0/0.04)]" aria-labelledby="tsn-03-h1">
<p class="font-tsn03-mono text-[11px] font-medium uppercase tracking-[0.16em] text-tsn03-acc">Journal</p>
<h2 id="tsn-03-h1" class="mt-3 max-w-[22ch] text-[clamp(24px,3.2vw,36px)] font-semibold leading-[1.1] tracking-[-0.025em] text-balance">Height to auto, without measuring</h2>
<p class="mt-4 max-w-[58ch] text-base leading-[1.7] text-tsn03-mut text-pretty">The panel animates <code class="rounded-md bg-tsn03-acc/10 px-1.5 py-0.5 font-tsn03-mono text-[0.86em] text-tsn03-ink">grid-rows-[0fr]</code> to <code class="rounded-md bg-tsn03-acc/10 px-1.5 py-0.5 font-tsn03-mono text-[0.86em] text-tsn03-ink">[1fr]</code>. The browser interpolates the track; no <code class="rounded-md bg-tsn03-acc/10 px-1.5 py-0.5 font-tsn03-mono text-[0.86em] text-tsn03-ink">scrollHeight</code> reads, no magic max-height.</p>
</section>
<section id="tsn-03-s2" class="scroll-mt-24 min-h-[44svh] rounded-[1.75rem] border border-tsn03-line bg-tsn03-surface p-[clamp(1.5rem,4vw,3rem)] shadow-[0_1px_2px_rgb(0_0_0/0.04)]" aria-labelledby="tsn-03-h2">
<p class="font-tsn03-mono text-[11px] font-medium uppercase tracking-[0.16em] text-tsn03-acc">Templates</p>
<h2 id="tsn-03-h2" class="mt-3 max-w-[22ch] text-[clamp(24px,3.2vw,36px)] font-semibold leading-[1.1] tracking-[-0.025em] text-balance">One attribute, every state</h2>
<p class="mt-4 max-w-[58ch] text-base leading-[1.7] text-tsn03-mut text-pretty">The script toggles <code class="rounded-md bg-tsn03-acc/10 px-1.5 py-0.5 font-tsn03-mono text-[0.86em] text-tsn03-ink">data-open</code>. The X morph, the panel and the stagger are all <code class="rounded-md bg-tsn03-acc/10 px-1.5 py-0.5 font-tsn03-mono text-[0.86em] text-tsn03-ink">group-data-open/hdr:</code> variants in the markup.</p>
</section>
<section id="tsn-03-s3" class="scroll-mt-24 min-h-[44svh] rounded-[1.75rem] border border-tsn03-line bg-tsn03-surface p-[clamp(1.5rem,4vw,3rem)] shadow-[0_1px_2px_rgb(0_0_0/0.04)]" aria-labelledby="tsn-03-h3">
<p class="font-tsn03-mono text-[11px] font-medium uppercase tracking-[0.16em] text-tsn03-acc">Sync</p>
<h2 id="tsn-03-h3" class="mt-3 max-w-[22ch] text-[clamp(24px,3.2vw,36px)] font-semibold leading-[1.1] tracking-[-0.025em] text-balance">Closed means unreachable</h2>
<p class="mt-4 max-w-[58ch] text-base leading-[1.7] text-tsn03-mut text-pretty">A collapsed panel is <code class="rounded-md bg-tsn03-acc/10 px-1.5 py-0.5 font-tsn03-mono text-[0.86em] text-tsn03-ink">inert</code>, so its links leave the tab order and the accessibility tree until the menu opens.</p>
</section>
<section id="tsn-03-s4" class="scroll-mt-24 min-h-[44svh] rounded-[1.75rem] border border-tsn03-line bg-tsn03-surface p-[clamp(1.5rem,4vw,3rem)] shadow-[0_1px_2px_rgb(0_0_0/0.04)]" aria-labelledby="tsn-03-h4">
<p class="font-tsn03-mono text-[11px] font-medium uppercase tracking-[0.16em] text-tsn03-acc">Pricing</p>
<h2 id="tsn-03-h4" class="mt-3 max-w-[22ch] text-[clamp(24px,3.2vw,36px)] font-semibold leading-[1.1] tracking-[-0.025em] text-balance">Escape always works</h2>
<p class="mt-4 max-w-[58ch] text-base leading-[1.7] text-tsn03-mut text-pretty">Escape closes the menu and returns focus to the toggle. Tapping a link closes it too, so the anchor lands on an uncovered page.</p>
</section>
</main>
<footer class="border-t border-tsn03-line px-[max(1.25rem,calc((100%-72rem)/2))] py-10 font-tsn03-mono text-xs text-tsn03-mut">Tailwind sticky navigation · pattern 03 of 25</footer>
</div>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tsn03-bg: #0f1110;
--color-tsn03-bg: oklch(0.17 0.006 150);
--color-tsn03-surface: #171a18;
--color-tsn03-surface: oklch(0.21 0.008 150);
--color-tsn03-ink: #eef2ea;
--color-tsn03-ink: oklch(0.95 0.012 130);
--color-tsn03-mut: #a3aa9d;
--color-tsn03-mut: oklch(0.72 0.018 130);
--color-tsn03-acc: #c6f24e;
--color-tsn03-acc: oklch(0.9 0.19 125);
--color-tsn03-line: #eef2ea1f;
--color-tsn03-line: oklch(0.95 0.012 130 / 0.12);
--font-tsn03-sans: 'Space Grotesk', system-ui, sans-serif;
--font-tsn03-mono: 'JetBrains Mono', ui-monospace, monospace;
}
/* Root rule: the full-bleed contract, restated in CSS so the page is sized before the browser build compiles. */
.tsn-03 {
width: 100%;
min-height: 100svh;
}
/* Smooth anchor jumps live on <html>, outside the markup, so they stay in CSS. */
@media (prefers-reduced-motion: no-preference) {
:root:has(.tsn-03) {
scroll-behavior: smooth;
}
}
```
## JavaScript
```js
(() => {
document.querySelectorAll('.tsn-03').forEach((root) => {
if (root.dataset.wired) return;
root.dataset.wired = '1';
const hdr = root.querySelector('[data-tsn03-header]');
const btn = root.querySelector('[data-tsn03-toggle]');
const panel = root.querySelector('#tsn-03-menu');
const set = (open) => {
hdr.toggleAttribute('data-open', open);
btn.setAttribute('aria-expanded', String(open));
panel.inert = !open;
};
btn.addEventListener('click', () => set(!hdr.hasAttribute('data-open')));
panel.addEventListener('click', (e) => { if (e.target.closest('a')) set(false); });
root.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && hdr.hasAttribute('data-open')) { set(false); btn.focus(); }
});
matchMedia('(min-width: 768px)').addEventListener('change', (e) => { if (e.matches) set(false); });
});
})();
```How this works
Height to auto, finally. The panel is a one-row grid whose track animates between 0fr and 1fr; its only child has overflow-hidden min-h-0, so the content is clipped while the row grows.
<div class="grid grid-rows-[0fr] transition-[grid-template-rows]
group-data-open/hdr:grid-rows-[1fr]">
<div class="min-h-0 overflow-hidden">…</div>The script owns state, not styles. It toggles data-open on the header, mirrors it into aria-expanded, and flips inert on the panel. Escape and link clicks close it; crossing the md breakpoint resets it.
Make it yours
- Slide-over drawer instead of push-down: make the panel
fixed inset-y-0 right-0 w-80 translate-x-full group-data-open/hdr:translate-x-0. - Breakpoint: change every
md:/max-md:pair together, and the768in the script'smatchMedia. - Faster feel: drop
duration-300toduration-200— the grid transition stays smooth.
Gotchas — read before shipping
- Animating
grid-template-rowsneeds Chrome 107+, Safari 16+; older engines snap open, which is still correct. - Forgetting
min-h-0on the grid child stops it from shrinking below its content — the panel never closes. - Without
inert, keyboard users tab into invisible links inside the collapsed panel.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
Grid-row interpolation: Chrome 107 / Safari 16 / Firefox 66. inert: Chrome 102 / Safari 15.5 / Firefox 112.