28 Tailwind Hamburger Menus
05 / 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 Checkbox Hack Hamburger Navigation (No JavaScript)

The classic zero-dependency pattern, and the port is the most instructive in the collection: peer-checked: handles the panel beautifully, but the burger's bars are descendants of a sibling — not siblings themselves — so they need arbitrary variants. That friction is exactly the argument for demo 09's has-checked:. Both accessibility fixes survive: the input stays focusable, and its focus ring is re-projected onto the visible label.

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-05 w-full min-h-svh grid justify-center content-start bg-thb05-bg font-thb05-sans text-thb05-ink px-5 py-9" aria-label="Pure Tailwind checkbox hack hamburger menu demo">
  <div class="relative w-[min(400px,88vw)] h-135 rounded-3xl overflow-hidden bg-thb05-bg border border-thb05-edge">
    <input type="checkbox" id="thb05-t" class="thb05-state peer absolute w-px h-px overflow-hidden whitespace-nowrap [clip-path:inset(50%)]">
    <header class="relative z-3 flex items-center justify-between h-17.5 pl-6 pr-4">
      <span class="flex items-center gap-2.25 text-[15px] font-bold tracking-[-0.01em]">
        <i class="w-4.75 h-4.75 rounded-full bg-[conic-gradient(from_160deg,var(--color-thb05-acc),oklch(0.75_0.13_110))]" aria-hidden="true"></i>papertrail
      </span>
      <label for="thb05-t" class="flex items-center gap-2.5 min-h-11 px-3.5 rounded-full border-[1.5px] border-thb05-ink cursor-pointer select-none text-[12.5px] font-bold tracking-[0.08em] uppercase transition-[background,color,border-color] duration-250 hover:bg-thb05-ink hover:text-thb05-bg peer-focus-visible:outline-2 peer-focus-visible:outline-thb05-acc peer-focus-visible:outline-offset-3 [.thb05-state:checked~header_&]:border-thb05-panelink [.thb05-state:checked~header_&]:text-thb05-panelink [.thb05-state:checked~header_&]:hover:bg-thb05-panelink [.thb05-state:checked~header_&]:hover:text-thb05-panel">
        <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-current transition-[translate,rotate] duration-300 [.thb05-state:checked~header_&]:translate-y-[5px] [.thb05-state:checked~header_&]:rotate-45 motion-reduce:transition-none"></i>
          <i class="absolute left-0 top-[5px] w-4.5 h-0.5 rounded-sm bg-current transition-[opacity] duration-200 [.thb05-state:checked~header_&]:opacity-0 motion-reduce:transition-none"></i>
          <i class="absolute left-0 top-2.5 w-4.5 h-0.5 rounded-sm bg-current transition-[translate,rotate] duration-300 [.thb05-state:checked~header_&]:-translate-y-[5px] [.thb05-state:checked~header_&]:-rotate-45 motion-reduce:transition-none"></i>
        </span>
        <span class="block [.thb05-state:checked~header_&]:hidden" aria-hidden="true">Menu</span>
        <span class="hidden [.thb05-state:checked~header_&]:block" aria-hidden="true">Close</span>
      </label>
    </header>
    <nav aria-label="Primary" class="absolute inset-0 z-2 flex flex-col justify-center gap-1 px-8 bg-thb05-panel -translate-y-[105%] invisible [transition:translate_.5s_var(--ease-thb05),visibility_0s_.5s] peer-checked:translate-y-0 peer-checked:visible peer-checked:[transition:translate_.5s_var(--ease-thb05),visibility_0s_0s] motion-reduce:[transition:opacity_.15s,visibility_0s_.15s]">
      <a href="#features" style="--i:0" class="py-3.25 px-0.5 text-[27px] font-bold tracking-[-0.02em] text-thb05-panelink no-underline border-b border-thb05-panedge opacity-0 -translate-y-3.5 transition-[color,opacity,translate] duration-350 ease-thb05in hover:text-thb05-panacc focus-visible:text-thb05-panacc focus-visible:outline-2 focus-visible:outline-thb05-panacc focus-visible:outline-offset-4 [.thb05-state:checked~nav_&]:opacity-100 [.thb05-state:checked~nav_&]:translate-y-0 [.thb05-state:checked~nav_&]:[transition-delay:calc(160ms+var(--i)*50ms)] motion-reduce:translate-y-0 motion-reduce:duration-150">Features</a>
      <a href="#pricing" style="--i:1" class="py-3.25 px-0.5 text-[27px] font-bold tracking-[-0.02em] text-thb05-panelink no-underline border-b border-thb05-panedge opacity-0 -translate-y-3.5 transition-[color,opacity,translate] duration-350 ease-thb05in hover:text-thb05-panacc focus-visible:text-thb05-panacc focus-visible:outline-2 focus-visible:outline-thb05-panacc focus-visible:outline-offset-4 [.thb05-state:checked~nav_&]:opacity-100 [.thb05-state:checked~nav_&]:translate-y-0 [.thb05-state:checked~nav_&]:[transition-delay:calc(160ms+var(--i)*50ms)] motion-reduce:translate-y-0 motion-reduce:duration-150">Pricing</a>
      <a href="#changelog" style="--i:2" class="py-3.25 px-0.5 text-[27px] font-bold tracking-[-0.02em] text-thb05-panelink no-underline border-b border-thb05-panedge opacity-0 -translate-y-3.5 transition-[color,opacity,translate] duration-350 ease-thb05in hover:text-thb05-panacc focus-visible:text-thb05-panacc focus-visible:outline-2 focus-visible:outline-thb05-panacc focus-visible:outline-offset-4 [.thb05-state:checked~nav_&]:opacity-100 [.thb05-state:checked~nav_&]:translate-y-0 [.thb05-state:checked~nav_&]:[transition-delay:calc(160ms+var(--i)*50ms)] motion-reduce:translate-y-0 motion-reduce:duration-150">Changelog</a>
      <a href="#docs" style="--i:3" class="py-3.25 px-0.5 text-[27px] font-bold tracking-[-0.02em] text-thb05-panelink no-underline border-b border-thb05-panedge opacity-0 -translate-y-3.5 transition-[color,opacity,translate] duration-350 ease-thb05in hover:text-thb05-panacc focus-visible:text-thb05-panacc focus-visible:outline-2 focus-visible:outline-thb05-panacc focus-visible:outline-offset-4 [.thb05-state:checked~nav_&]:opacity-100 [.thb05-state:checked~nav_&]:translate-y-0 [.thb05-state:checked~nav_&]:[transition-delay:calc(160ms+var(--i)*50ms)] motion-reduce:translate-y-0 motion-reduce:duration-150">Docs</a>
      <a href="#signin" style="--i:4" class="mt-4.5 py-3.25 px-0.5 text-base font-bold tracking-[-0.02em] text-thb05-panacc no-underline opacity-0 -translate-y-3.5 transition-[color,opacity,translate] duration-350 ease-thb05in hover:text-thb05-panelink focus-visible:outline-2 focus-visible:outline-thb05-panacc focus-visible:outline-offset-4 [.thb05-state:checked~nav_&]:opacity-100 [.thb05-state:checked~nav_&]:translate-y-0 [.thb05-state:checked~nav_&]:[transition-delay:calc(160ms+var(--i)*50ms)] motion-reduce:translate-y-0 motion-reduce:duration-150">Sign in &rarr;</a>
    </nav>
    <div class="grid gap-3.25 px-6 pt-4" aria-hidden="true">
      <div class="flex flex-col justify-end h-42.5 mb-1.5 p-4.5 rounded-2xl bg-linear-[150deg,oklch(0.9_0.04_120),oklch(0.84_0.06_145)]">
        <b class="text-[26px] font-bold tracking-[-0.02em]">0 bytes</b>
        <span class="text-[12.5px] text-thb05-mut">of JavaScript in this menu</span>
      </div>
      <div class="h-2.75 w-[84%] rounded-full bg-thb05-line"></div>
      <div class="h-2.75 w-[66%] rounded-full bg-thb05-line"></div>
      <div class="h-2.75 w-[75%] rounded-full bg-thb05-line"></div>
    </div>
  </div>
</section>
@import "tailwindcss";

@theme {
  --color-thb05-bg: #f5f4e9;
  --color-thb05-bg: oklch(0.96 0.012 100);
  --color-thb05-panel: #2f4438;
  --color-thb05-panel: oklch(0.27 0.04 145);
  --color-thb05-panelink: #eef2ec;
  --color-thb05-panelink: oklch(0.95 0.02 145);
  --color-thb05-panedge: #47604f;
  --color-thb05-panedge: oklch(0.36 0.04 145);
  --color-thb05-panacc: #cbd96a;
  --color-thb05-panacc: oklch(0.85 0.14 110);
  --color-thb05-edge: #dcdac9;
  --color-thb05-edge: oklch(0.87 0.02 100);
  --color-thb05-line: #e2e0d0;
  --color-thb05-line: oklch(0.89 0.012 100);
  --color-thb05-ink: #363c33;
  --color-thb05-ink: oklch(0.26 0.02 130);
  --color-thb05-mut: #6f766a;
  --color-thb05-mut: oklch(0.52 0.02 130);
  --color-thb05-acc: #5a8f5f;
  --color-thb05-acc: oklch(0.55 0.13 145);
  --font-thb05-sans: 'Space Grotesk', system-ui, sans-serif;
  --font-thb05-mono: 'JetBrains Mono', ui-monospace, monospace;
  --ease-thb05: cubic-bezier(0.7, 0, 0.25, 1);
  --ease-thb05in: cubic-bezier(0.2, 0.7, 0.2, 1);
}
/* 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-05 {
  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-thb05-bg: #f5f4e9;
  --color-thb05-bg: oklch(0.96 0.012 100);
  --color-thb05-panel: #2f4438;
  --color-thb05-panel: oklch(0.27 0.04 145);
  --color-thb05-panelink: #eef2ec;
  --color-thb05-panelink: oklch(0.95 0.02 145);
  --color-thb05-panedge: #47604f;
  --color-thb05-panedge: oklch(0.36 0.04 145);
  --color-thb05-panacc: #cbd96a;
  --color-thb05-panacc: oklch(0.85 0.14 110);
  --color-thb05-edge: #dcdac9;
  --color-thb05-edge: oklch(0.87 0.02 100);
  --color-thb05-line: #e2e0d0;
  --color-thb05-line: oklch(0.89 0.012 100);
  --color-thb05-ink: #363c33;
  --color-thb05-ink: oklch(0.26 0.02 130);
  --color-thb05-mut: #6f766a;
  --color-thb05-mut: oklch(0.52 0.02 130);
  --color-thb05-acc: #5a8f5f;
  --color-thb05-acc: oklch(0.55 0.13 145);
  --font-thb05-sans: 'Space Grotesk', system-ui, sans-serif;
  --font-thb05-mono: 'JetBrains Mono', ui-monospace, monospace;
  --ease-thb05: cubic-bezier(0.7, 0, 0.25, 1);
  --ease-thb05in: cubic-bezier(0.2, 0.7, 0.2, 1);
}
/* 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-05 {
  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&family=JetBrains+Mono:wght@400;500;600&display=swap">

What changed in the port

Which parts became utilities, which stayed CSS, and why.

  • :checked ~ nav becomes peer-checked: and reads better than the CSS version. :checked ~ header .burger i has no peer-* form at all, because peer-* stops at siblings — those seven declarations become arbitrary variants keyed to a hook class on the input.
  • That friction is the point of the demo. It is the strongest practical argument for :has(): demo 09 rewrites the same menu with has-checked:, where every descendant reads the state from an ancestor and no DOM-shape knowledge is needed.
  • The visually-hidden-but-focusable recipe is four utilities plus one arbitrary [clip-path:inset(50%)]. Tailwind's own sr-only would also work here — it is deliberately spelled out so the &ldquo;never hidden&rdquo; rule is visible in the markup.
  • peer-focus-visible:outline-2 on the label is the port's neatest line: the focus-ring re-projection that most tutorials omit becomes a single utility.
  • The Menu/Close text swap stays pure CSS — two spans, block and hidden, flipped by the same arbitrary variant.
  • Zero JavaScript, and the entry ships an empty js field to prove it.
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 Checkbox Hack Hamburger Navigation (No JavaScript)
Source: https://codefronts.com/navigation/tailwind-hamburger-menus/pure-tailwind-checkbox-hack-hamburger-navigation-no-javascript/

The classic zero-dependency pattern, and the port is the most instructive in the collection: peer-checked: handles the panel beautifully, but the burger's bars are descendants of a sibling &mdash; not siblings themselves &mdash; so they need arbitrary variants. That friction is exactly the argument for demo 09's has-checked:. Both accessibility fixes survive: the input stays focusable, and its focus ring is re-projected onto the visible label.
## HTML
```html
<section class="thb-05 w-full min-h-svh grid justify-center content-start bg-thb05-bg font-thb05-sans text-thb05-ink px-5 py-9" aria-label="Pure Tailwind checkbox hack hamburger menu demo">
  <div class="relative w-[min(400px,88vw)] h-135 rounded-3xl overflow-hidden bg-thb05-bg border border-thb05-edge">
    <input type="checkbox" id="thb05-t" class="thb05-state peer absolute w-px h-px overflow-hidden whitespace-nowrap [clip-path:inset(50%)]">
    <header class="relative z-3 flex items-center justify-between h-17.5 pl-6 pr-4">
      <span class="flex items-center gap-2.25 text-[15px] font-bold tracking-[-0.01em]">
        <i class="w-4.75 h-4.75 rounded-full bg-[conic-gradient(from_160deg,var(--color-thb05-acc),oklch(0.75_0.13_110))]" aria-hidden="true"></i>papertrail
      </span>
      <label for="thb05-t" class="flex items-center gap-2.5 min-h-11 px-3.5 rounded-full border-[1.5px] border-thb05-ink cursor-pointer select-none text-[12.5px] font-bold tracking-[0.08em] uppercase transition-[background,color,border-color] duration-250 hover:bg-thb05-ink hover:text-thb05-bg peer-focus-visible:outline-2 peer-focus-visible:outline-thb05-acc peer-focus-visible:outline-offset-3 [.thb05-state:checked~header_&]:border-thb05-panelink [.thb05-state:checked~header_&]:text-thb05-panelink [.thb05-state:checked~header_&]:hover:bg-thb05-panelink [.thb05-state:checked~header_&]:hover:text-thb05-panel">
        <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-current transition-[translate,rotate] duration-300 [.thb05-state:checked~header_&]:translate-y-[5px] [.thb05-state:checked~header_&]:rotate-45 motion-reduce:transition-none"></i>
          <i class="absolute left-0 top-[5px] w-4.5 h-0.5 rounded-sm bg-current transition-[opacity] duration-200 [.thb05-state:checked~header_&]:opacity-0 motion-reduce:transition-none"></i>
          <i class="absolute left-0 top-2.5 w-4.5 h-0.5 rounded-sm bg-current transition-[translate,rotate] duration-300 [.thb05-state:checked~header_&]:-translate-y-[5px] [.thb05-state:checked~header_&]:-rotate-45 motion-reduce:transition-none"></i>
        </span>
        <span class="block [.thb05-state:checked~header_&]:hidden" aria-hidden="true">Menu</span>
        <span class="hidden [.thb05-state:checked~header_&]:block" aria-hidden="true">Close</span>
      </label>
    </header>
    <nav aria-label="Primary" class="absolute inset-0 z-2 flex flex-col justify-center gap-1 px-8 bg-thb05-panel -translate-y-[105%] invisible [transition:translate_.5s_var(--ease-thb05),visibility_0s_.5s] peer-checked:translate-y-0 peer-checked:visible peer-checked:[transition:translate_.5s_var(--ease-thb05),visibility_0s_0s] motion-reduce:[transition:opacity_.15s,visibility_0s_.15s]">
      <a href="#features" style="--i:0" class="py-3.25 px-0.5 text-[27px] font-bold tracking-[-0.02em] text-thb05-panelink no-underline border-b border-thb05-panedge opacity-0 -translate-y-3.5 transition-[color,opacity,translate] duration-350 ease-thb05in hover:text-thb05-panacc focus-visible:text-thb05-panacc focus-visible:outline-2 focus-visible:outline-thb05-panacc focus-visible:outline-offset-4 [.thb05-state:checked~nav_&]:opacity-100 [.thb05-state:checked~nav_&]:translate-y-0 [.thb05-state:checked~nav_&]:[transition-delay:calc(160ms+var(--i)*50ms)] motion-reduce:translate-y-0 motion-reduce:duration-150">Features</a>
      <a href="#pricing" style="--i:1" class="py-3.25 px-0.5 text-[27px] font-bold tracking-[-0.02em] text-thb05-panelink no-underline border-b border-thb05-panedge opacity-0 -translate-y-3.5 transition-[color,opacity,translate] duration-350 ease-thb05in hover:text-thb05-panacc focus-visible:text-thb05-panacc focus-visible:outline-2 focus-visible:outline-thb05-panacc focus-visible:outline-offset-4 [.thb05-state:checked~nav_&]:opacity-100 [.thb05-state:checked~nav_&]:translate-y-0 [.thb05-state:checked~nav_&]:[transition-delay:calc(160ms+var(--i)*50ms)] motion-reduce:translate-y-0 motion-reduce:duration-150">Pricing</a>
      <a href="#changelog" style="--i:2" class="py-3.25 px-0.5 text-[27px] font-bold tracking-[-0.02em] text-thb05-panelink no-underline border-b border-thb05-panedge opacity-0 -translate-y-3.5 transition-[color,opacity,translate] duration-350 ease-thb05in hover:text-thb05-panacc focus-visible:text-thb05-panacc focus-visible:outline-2 focus-visible:outline-thb05-panacc focus-visible:outline-offset-4 [.thb05-state:checked~nav_&]:opacity-100 [.thb05-state:checked~nav_&]:translate-y-0 [.thb05-state:checked~nav_&]:[transition-delay:calc(160ms+var(--i)*50ms)] motion-reduce:translate-y-0 motion-reduce:duration-150">Changelog</a>
      <a href="#docs" style="--i:3" class="py-3.25 px-0.5 text-[27px] font-bold tracking-[-0.02em] text-thb05-panelink no-underline border-b border-thb05-panedge opacity-0 -translate-y-3.5 transition-[color,opacity,translate] duration-350 ease-thb05in hover:text-thb05-panacc focus-visible:text-thb05-panacc focus-visible:outline-2 focus-visible:outline-thb05-panacc focus-visible:outline-offset-4 [.thb05-state:checked~nav_&]:opacity-100 [.thb05-state:checked~nav_&]:translate-y-0 [.thb05-state:checked~nav_&]:[transition-delay:calc(160ms+var(--i)*50ms)] motion-reduce:translate-y-0 motion-reduce:duration-150">Docs</a>
      <a href="#signin" style="--i:4" class="mt-4.5 py-3.25 px-0.5 text-base font-bold tracking-[-0.02em] text-thb05-panacc no-underline opacity-0 -translate-y-3.5 transition-[color,opacity,translate] duration-350 ease-thb05in hover:text-thb05-panelink focus-visible:outline-2 focus-visible:outline-thb05-panacc focus-visible:outline-offset-4 [.thb05-state:checked~nav_&]:opacity-100 [.thb05-state:checked~nav_&]:translate-y-0 [.thb05-state:checked~nav_&]:[transition-delay:calc(160ms+var(--i)*50ms)] motion-reduce:translate-y-0 motion-reduce:duration-150">Sign in &rarr;</a>
    </nav>
    <div class="grid gap-3.25 px-6 pt-4" aria-hidden="true">
      <div class="flex flex-col justify-end h-42.5 mb-1.5 p-4.5 rounded-2xl bg-linear-[150deg,oklch(0.9_0.04_120),oklch(0.84_0.06_145)]">
        <b class="text-[26px] font-bold tracking-[-0.02em]">0 bytes</b>
        <span class="text-[12.5px] text-thb05-mut">of JavaScript in this menu</span>
      </div>
      <div class="h-2.75 w-[84%] rounded-full bg-thb05-line"></div>
      <div class="h-2.75 w-[66%] rounded-full bg-thb05-line"></div>
      <div class="h-2.75 w-[75%] rounded-full bg-thb05-line"></div>
    </div>
  </div>
</section>
```
## CSS
```css
@import "tailwindcss";

@theme {
  --color-thb05-bg: #f5f4e9;
  --color-thb05-bg: oklch(0.96 0.012 100);
  --color-thb05-panel: #2f4438;
  --color-thb05-panel: oklch(0.27 0.04 145);
  --color-thb05-panelink: #eef2ec;
  --color-thb05-panelink: oklch(0.95 0.02 145);
  --color-thb05-panedge: #47604f;
  --color-thb05-panedge: oklch(0.36 0.04 145);
  --color-thb05-panacc: #cbd96a;
  --color-thb05-panacc: oklch(0.85 0.14 110);
  --color-thb05-edge: #dcdac9;
  --color-thb05-edge: oklch(0.87 0.02 100);
  --color-thb05-line: #e2e0d0;
  --color-thb05-line: oklch(0.89 0.012 100);
  --color-thb05-ink: #363c33;
  --color-thb05-ink: oklch(0.26 0.02 130);
  --color-thb05-mut: #6f766a;
  --color-thb05-mut: oklch(0.52 0.02 130);
  --color-thb05-acc: #5a8f5f;
  --color-thb05-acc: oklch(0.55 0.13 145);
  --font-thb05-sans: 'Space Grotesk', system-ui, sans-serif;
  --font-thb05-mono: 'JetBrains Mono', ui-monospace, monospace;
  --ease-thb05: cubic-bezier(0.7, 0, 0.25, 1);
  --ease-thb05in: cubic-bezier(0.2, 0.7, 0.2, 1);
}
/* 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-05 {
  width: 100%;
  min-height: 100svh;
}
```

How this works

The engine is two class names. The checkbox is the boolean; peer-checked: broadcasts it to any LATER sibling:

<input type="checkbox" id="thb05-t"
  class="peer thb05-state absolute w-px h-px [clip-path:inset(50%)]">
<header><label for="thb05-t">…</label></header>
<nav class="-translate-y-[105%] invisible
  peer-checked:translate-y-0 peer-checked:visible">…</nav>

Where the port gets honest. peer-* compiles to .peer:checked ~ .target — it only reaches siblings. The three bars inside the label are descendants of a sibling, so they need the arbitrary-variant escape hatch:

class="[.thb05-state:checked~header_&]:translate-y-[5px]
       [.thb05-state:checked~header_&]:rotate-45"

That is legal, statically compilable and genuinely ugly. It is also the clearest motivation in this collection for the :has() rewrite: has-checked: reads state from an ancestor, so every descendant can respond without knowing the DOM shape. Demo 09 does exactly that.

Fix #1 — never hidden the input. hidden removes it from the tab order and keyboard users lose the menu entirely. The clip recipe keeps it focusable, so Space still toggles it.

Fix #2 — show that focus. When the invisible input has keyboard focus, peer-focus-visible:outline-2 on the label paints a ring on the thing the user can actually see.

Honest limits, printed because tutorials skip them: a label announces as “checkbox”, not “button, expanded”; Escape cannot close it; focus cannot be moved programmatically. For a marketing site that trade is often fine. For app navigation, use demos 02, 04 or 17.

Make it yours

  • Panel entrance: this one drops from the top (-translate-y-[105%]). Swap for -translate-x-full and it is a left drawer — the checkbox engine does not care.
  • Click-outside-to-close for free: add a second full-bleed <label for=&quot;thb05-t&quot;> behind the panel as a scrim. Labels are this pattern's buttons, and you can have as many as you like.
  • The Menu/Close word swap is two spans and two utilities; delete both for an icon-only burger.
  • Retheme through --color-thb05-panel and --color-thb05-acc in @theme.
  • Auto-close after a link click is the one thing CSS cannot do. Accept it, or move to demo 02's button pattern.

Gotchas — read before shipping

  • hidden, invisible or sr-only-with-display:none on the checkbox breaks keyboard access — the #1 checkbox-hack bug in the wild. Clip it and keep it focusable.
  • peer-* only reaches later siblings of the same parent. The moment the panel lives in another subtree the pattern dies — restructure, or move to has-checked: (demo 09).
  • Arbitrary variants like [.thb05-state:checked~header_&]: need a stable hook class on the input and underscores for every space. They are also invisible to a reader skimming the markup; comment them.
  • Screen readers announce &ldquo;checked/unchecked&rdquo;, not &ldquo;expanded/collapsed&rdquo;, and no ARIA fixes it: role=&quot;button&quot; on a label that toggles a checkbox lies about Space/Enter behaviour. It is the pattern's real cost.
  • iOS Safari ignores taps on an empty label. The visible bars inside this one are what keep it tappable.
  • Two of these menus on one page need two ids AND two hook classes; the arbitrary variant is not scoped by anything else.

Browser support

ChromeSafariFirefoxEdge
111+16.4+128+111+

The :checked ~ engine works in every browser since IE9, so this is the most compatible pattern in the collection once compiled; the floor quoted here is Tailwind v4's own generated sheet.

Techniques used in this demo

Theme tokensset in @theme, used by a utility

Arbitraryneeds bracket syntax

Search CodeFronts

Loading…