05 / 28
All 28 in 28 Tailwind Hamburger Menus
- 01Tailwind Hamburger Menu with Clip-Path Circle Expansion
- 02Tailwind Keyboard Navigable Hamburger Menu with ARIA Attributes
- 03Tailwind Animated 3-Line to X Hamburger Icon Toggle
- 04Tailwind Slide-In Off-Canvas Drawer with Dimmed Backdrop Overlay
- 05Pure Tailwind Checkbox Hack Hamburger Navigation (No JavaScript)
- 06Tailwind Responsive Hamburger Menu with Integrated Search Bar & Social Links
- 07Tailwind Glassmorphism Backdrop Blur Hamburger Navigation
- 08Tailwind Push Page Navigation Menu (Off-Canvas Content Shift)
- 09Pure Tailwind Hamburger Menu Without JavaScript
- 10Tailwind Hamburger Menu Morphing to Close Icon Animation
- 11Tailwind Full Screen Mobile Navigation Overlay
- 12Tailwind Slide Out Sidebar Navigation / Off Canvas Menu
- 13Tailwind Responsive Navbar Mobile Hamburger Toggle Breakpoints
- 14Tailwind Minimalist Floating Hamburger Button
- 15Tailwind CSS Responsive Hamburger Menu Component
- 16Tailwind Rebuild of the Bootstrap 5 Navbar Hamburger Toggle
- 17Tailwind WCAG Accessible Hamburger Menu Example
- 18Pure Tailwind Dropdown Slide Down Mobile Hamburger Menu
- 19Simple 3-Line Tailwind Hamburger Icon Hover Effect
- 20Tailwind Full Screen Blur Overlay Hamburger Menu
- 21Tailwind Mobile Bottom Navigation Bar with Floating Hamburger Toggle
- 22Pure Tailwind Multi-Level Accordion Hamburger Dropdown Menu
- 23Flexbox Navbar Right-Aligned Tailwind Hamburger Icon
- 24Radial Ripple Expand Tailwind Hamburger Menu
- 25Sticky Tailwind Navbar Shrinking Hamburger Icon on Scroll
- 26Neumorphic Tailwind Hamburger Toggle Button
- 27Brutalist Design Bordered Tailwind Hamburger Menu
- 28Tailwind 3D Flip Canvas Mobile Hamburger Navigation
Pure Tailwind 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.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
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 →</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><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 →</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;
}@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.
<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-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;
}@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;
}<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.
:checked ~ navbecomespeer-checked:and reads better than the CSS version.:checked ~ header .burger ihas nopeer-*form at all, becausepeer-*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 withhas-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 ownsr-onlywould also work here — it is deliberately spelled out so the “neverhidden” rule is visible in the markup. peer-focus-visible:outline-2on 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,
blockandhidden, flipped by the same arbitrary variant. - Zero JavaScript, and the entry ships an empty
jsfield to prove it.
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 — 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.
## 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 →</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;
}
```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 — 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.
## 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 →</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-fulland it is a left drawer — the checkbox engine does not care. - Click-outside-to-close for free: add a second full-bleed
<label for="thb05-t">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-paneland--color-thb05-accin@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,invisibleorsr-only-with-display:noneon 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 tohas-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 “checked/unchecked”, not “expanded/collapsed”, and no ARIA fixes it:
role="button"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
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 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.