28 Tailwind Hamburger Menus
12 / 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
Tailwind + JSMIT licensed

Tailwind Slide Out Sidebar Navigation / Off Canvas Menu

The workhorse app sidebar, structured the way real products need it: grouped sections, icon+label rows with an aria-current-driven indicator bar, shortcut hints, and a user card pinned to the bottom. The slide engine is demo 04's; what this demo teaches is interior architecture — and in utilities the active-state contract (aria-current:) is visible on the row itself.

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-12 w-full min-h-svh grid justify-center content-start bg-thb12-bg font-thb12-sans text-thb12-ink px-5 py-9" aria-label="Slide out sidebar navigation demo">
  <div class="group relative w-[min(420px,88vw)] h-140 rounded-3xl overflow-hidden bg-thb12-bg border border-thb12-edge" style="--w:288px">
    <header class="flex items-center gap-3 h-16 px-4 bg-white border-b border-thb12-edge">
      <button type="button" aria-expanded="false" aria-controls="thb12-drawer" aria-label="Open navigation" class="group/btn grid place-items-center w-11 h-11 rounded-xl border-0 bg-transparent text-thb12-ink cursor-pointer transition-[background] duration-200 hover:bg-thb12-hover focus-visible:outline-2 focus-visible:outline-thb12-acc focus-visible:outline-offset-2">
        <span class="relative block w-4.75 h-3.25" aria-hidden="true">
          <i class="absolute left-0 top-0 w-4.75 h-0.5 rounded-sm bg-current transition-[width] duration-250"></i>
          <i class="absolute left-0 top-[5.5px] w-3.25 h-0.5 rounded-sm bg-current transition-[width] duration-250 group-hover/btn:w-4.75"></i>
          <i class="absolute left-0 top-[11px] w-4 h-0.5 rounded-sm bg-current transition-[width] duration-250 group-hover/btn:w-4.75"></i>
        </span>
      </button>
      <strong class="text-[15px] font-bold tracking-[-0.01em]">Tessera</strong>
      <span class="text-[13px] text-thb12-mut" aria-hidden="true">/ Q3 planning</span>
    </header>
    <div class="grid gap-3.25 p-4.5" aria-hidden="true">
      <div class="h-40 rounded-[15px] bg-linear-[145deg,oklch(0.92_0.02_190),oklch(0.88_0.035_165)]"></div>
      <div class="flex gap-3">
        <span class="flex-1 h-19.5 rounded-[13px] bg-white border border-thb12-edge"></span>
        <span class="flex-1 h-19.5 rounded-[13px] bg-white border border-thb12-edge"></span>
      </div>
      <div class="h-2.75 w-[78%] rounded-full bg-thb12-line"></div>
      <div class="h-2.75 w-[62%] rounded-full bg-thb12-line"></div>
    </div>
    <button type="button" tabindex="-1" aria-hidden="true" class="absolute inset-0 z-2 border-0 p-0 cursor-pointer opacity-0 invisible bg-[oklch(0.2_0.02_260/0.4)] [transition:opacity_.32s,visibility_0s_.32s] group-data-open:opacity-100 group-data-open:visible group-data-open:[transition:opacity_.32s,visibility_0s_0s]"></button>
    <aside id="thb12-drawer" aria-label="Navigation" class="absolute inset-y-0 left-0 z-3 grid grid-rows-[auto_1fr_auto] w-[min(88%,var(--w))] bg-thb12-dark text-thb12-dink shadow-[24px_0_48px_-22px_oklch(0.15_0.03_262/0.6)] -translate-x-[102%] invisible [transition:translate_.42s_var(--ease-thb12),visibility_0s_.42s] group-data-open:translate-x-0 group-data-open:visible group-data-open:[transition:translate_.42s_var(--ease-thb12),visibility_0s_0s] motion-reduce:[transition:opacity_.15s,visibility_0s_.15s]">
      <header class="flex items-center gap-2.5 pt-4.5 pb-3.5 pl-4.5 pr-3.5">
        <span class="grid place-items-center w-7 h-7 rounded-[9px] bg-linear-[140deg,var(--color-thb12-acc),oklch(0.6_0.12_210)] text-[13px] font-extrabold text-thb12-dark" aria-hidden="true">T</span>
        <b class="text-[15px] font-bold">Tessera</b>
        <button type="button" aria-label="Close navigation" class="grid place-items-center ml-auto w-9.5 h-9.5 rounded-[10px] border border-thb12-dedge bg-transparent text-thb12-dmut cursor-pointer transition-[color,border-color] duration-200 hover:text-thb12-dink hover:border-thb12-dedge2 focus-visible:outline-2 focus-visible:outline-thb12-acc focus-visible:outline-offset-2">
          <svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true"><path d="m6 6 12 12M18 6 6 18"/></svg>
        </button>
      </header>
      <nav aria-label="Primary" class="min-h-0 overflow-auto overscroll-contain px-3 pt-1.5 pb-3.5">
        <div style="--g:0" class="[&+div]:mt-4.5">
          <span class="block px-2.5 pb-1.75 font-thb12-mono text-[10px] font-bold tracking-[0.14em] uppercase text-thb12-dcap">Workspace</span>
          <a href="#board" aria-current="page" style="--g:0" class="relative flex items-center gap-3 min-h-11 pl-3.5 pr-2.5 rounded-[10px] no-underline text-[13.5px] font-semibold text-thb12-dmut opacity-0 -translate-x-3.5 transition-[background,color,opacity,translate] duration-350 ease-thb12 hover:bg-[oklch(1_0_0/0.06)] hover:text-thb12-dink focus-visible:text-thb12-dink focus-visible:outline-2 focus-visible:outline-thb12-acc focus-visible:-outline-offset-2 aria-current:text-thb12-dink aria-current:bg-[oklch(1_0_0/0.07)] group-data-open:opacity-100 group-data-open:translate-x-0 group-data-open:[transition-delay:calc(100ms+var(--g)*60ms)] motion-reduce:translate-x-0 motion-reduce:duration-150">
            <span class="absolute left-0.5 top-[20%] w-[3.5px] h-[60%] rounded-full bg-thb12-acc scale-y-0 transition-[scale] duration-250 aria-current:scale-y-100" aria-hidden="true"></span>
            <svg viewBox="0 0 24 24" class="w-4.5 h-4.5 shrink-0 fill-none stroke-current stroke-[1.7] [stroke-linecap:round] [stroke-linejoin:round] aria-current:stroke-thb12-acc" aria-hidden="true"><rect x="3.5" y="3.5" width="7.5" height="10" rx="1.5"/><rect x="13" y="3.5" width="7.5" height="6" rx="1.5"/><rect x="13" y="11.5" width="7.5" height="9" rx="1.5"/><rect x="3.5" y="15.5" width="7.5" height="5" rx="1.5"/></svg>Board
            <kbd class="ml-auto px-1.5 py-0.5 rounded-[5px] border border-thb12-dedge font-thb12-mono text-[10px] font-semibold text-thb12-dcap" aria-hidden="true">&#8984;1</kbd>
          </a>
          <a href="#timeline" style="--g:0" class="relative flex items-center gap-3 min-h-11 pl-3.5 pr-2.5 rounded-[10px] no-underline text-[13.5px] font-semibold text-thb12-dmut opacity-0 -translate-x-3.5 transition-[background,color,opacity,translate] duration-350 ease-thb12 hover:bg-[oklch(1_0_0/0.06)] hover:text-thb12-dink focus-visible:text-thb12-dink focus-visible:outline-2 focus-visible:outline-thb12-acc focus-visible:-outline-offset-2 group-data-open:opacity-100 group-data-open:translate-x-0 group-data-open:[transition-delay:calc(100ms+var(--g)*60ms)] motion-reduce:translate-x-0 motion-reduce:duration-150">
            <svg viewBox="0 0 24 24" class="w-4.5 h-4.5 shrink-0 fill-none stroke-current stroke-[1.7] [stroke-linecap:round] [stroke-linejoin:round]" aria-hidden="true"><path d="M3.5 6h11M3.5 12h17M3.5 18h8"/><circle cx="18" cy="6" r="2"/><circle cx="15" cy="18" r="2"/></svg>Timeline
            <kbd class="ml-auto px-1.5 py-0.5 rounded-[5px] border border-thb12-dedge font-thb12-mono text-[10px] font-semibold text-thb12-dcap" aria-hidden="true">&#8984;2</kbd>
          </a>
          <a href="#backlog" style="--g:0" class="relative flex items-center gap-3 min-h-11 pl-3.5 pr-2.5 rounded-[10px] no-underline text-[13.5px] font-semibold text-thb12-dmut opacity-0 -translate-x-3.5 transition-[background,color,opacity,translate] duration-350 ease-thb12 hover:bg-[oklch(1_0_0/0.06)] hover:text-thb12-dink focus-visible:text-thb12-dink focus-visible:outline-2 focus-visible:outline-thb12-acc focus-visible:-outline-offset-2 group-data-open:opacity-100 group-data-open:translate-x-0 group-data-open:[transition-delay:calc(100ms+var(--g)*60ms)] motion-reduce:translate-x-0 motion-reduce:duration-150">
            <svg viewBox="0 0 24 24" class="w-4.5 h-4.5 shrink-0 fill-none stroke-current stroke-[1.7] [stroke-linecap:round] [stroke-linejoin:round]" aria-hidden="true"><path d="M4 6h16M4 12h16M4 18h16"/><circle cx="4" cy="6" r="1.4"/><circle cx="4" cy="12" r="1.4"/><circle cx="4" cy="18" r="1.4"/></svg>Backlog
            <b class="ml-auto px-1.75 py-0.5 rounded-full bg-thb12-acc text-thb12-dark text-[10.5px] font-bold">24</b>
          </a>
        </div>
        <div style="--g:1" class="[&+div]:mt-4.5">
          <span class="block px-2.5 pb-1.75 font-thb12-mono text-[10px] font-bold tracking-[0.14em] uppercase text-thb12-dcap">Library</span>
          <a href="#docs" style="--g:1" class="relative flex items-center gap-3 min-h-11 pl-3.5 pr-2.5 rounded-[10px] no-underline text-[13.5px] font-semibold text-thb12-dmut opacity-0 -translate-x-3.5 transition-[background,color,opacity,translate] duration-350 ease-thb12 hover:bg-[oklch(1_0_0/0.06)] hover:text-thb12-dink focus-visible:text-thb12-dink focus-visible:outline-2 focus-visible:outline-thb12-acc focus-visible:-outline-offset-2 group-data-open:opacity-100 group-data-open:translate-x-0 group-data-open:[transition-delay:calc(100ms+var(--g)*60ms)] motion-reduce:translate-x-0 motion-reduce:duration-150">
            <svg viewBox="0 0 24 24" class="w-4.5 h-4.5 shrink-0 fill-none stroke-current stroke-[1.7] [stroke-linecap:round] [stroke-linejoin:round]" aria-hidden="true"><path d="M5.5 4h9l4 4v12h-13Z"/><path d="M14 4v4.5h4.5"/></svg>Documents
          </a>
          <a href="#assets" style="--g:1" class="relative flex items-center gap-3 min-h-11 pl-3.5 pr-2.5 rounded-[10px] no-underline text-[13.5px] font-semibold text-thb12-dmut opacity-0 -translate-x-3.5 transition-[background,color,opacity,translate] duration-350 ease-thb12 hover:bg-[oklch(1_0_0/0.06)] hover:text-thb12-dink focus-visible:text-thb12-dink focus-visible:outline-2 focus-visible:outline-thb12-acc focus-visible:-outline-offset-2 group-data-open:opacity-100 group-data-open:translate-x-0 group-data-open:[transition-delay:calc(100ms+var(--g)*60ms)] motion-reduce:translate-x-0 motion-reduce:duration-150">
            <svg viewBox="0 0 24 24" class="w-4.5 h-4.5 shrink-0 fill-none stroke-current stroke-[1.7] [stroke-linecap:round] [stroke-linejoin:round]" aria-hidden="true"><rect x="3.5" y="5" width="17" height="14" rx="2"/><circle cx="9" cy="10.5" r="1.8"/><path d="m3.5 16.5 5-4 4 3.5 3-2.5 5 3.5"/></svg>Assets
          </a>
          <a href="#reports" style="--g:1" class="relative flex items-center gap-3 min-h-11 pl-3.5 pr-2.5 rounded-[10px] no-underline text-[13.5px] font-semibold text-thb12-dmut opacity-0 -translate-x-3.5 transition-[background,color,opacity,translate] duration-350 ease-thb12 hover:bg-[oklch(1_0_0/0.06)] hover:text-thb12-dink focus-visible:text-thb12-dink focus-visible:outline-2 focus-visible:outline-thb12-acc focus-visible:-outline-offset-2 group-data-open:opacity-100 group-data-open:translate-x-0 group-data-open:[transition-delay:calc(100ms+var(--g)*60ms)] motion-reduce:translate-x-0 motion-reduce:duration-150">
            <svg viewBox="0 0 24 24" class="w-4.5 h-4.5 shrink-0 fill-none stroke-current stroke-[1.7] [stroke-linecap:round] [stroke-linejoin:round]" aria-hidden="true"><path d="M5 20V9M12 20V4M19 20v-7"/></svg>Reports
          </a>
        </div>
        <div style="--g:2">
          <span class="block px-2.5 pb-1.75 font-thb12-mono text-[10px] font-bold tracking-[0.14em] uppercase text-thb12-dcap">Support</span>
          <a href="#help" style="--g:2" class="relative flex items-center gap-3 min-h-11 pl-3.5 pr-2.5 rounded-[10px] no-underline text-[13.5px] font-semibold text-thb12-dmut opacity-0 -translate-x-3.5 transition-[background,color,opacity,translate] duration-350 ease-thb12 hover:bg-[oklch(1_0_0/0.06)] hover:text-thb12-dink focus-visible:text-thb12-dink focus-visible:outline-2 focus-visible:outline-thb12-acc focus-visible:-outline-offset-2 group-data-open:opacity-100 group-data-open:translate-x-0 group-data-open:[transition-delay:calc(100ms+var(--g)*60ms)] motion-reduce:translate-x-0 motion-reduce:duration-150">
            <svg viewBox="0 0 24 24" class="w-4.5 h-4.5 shrink-0 fill-none stroke-current stroke-[1.7] [stroke-linecap:round] [stroke-linejoin:round]" aria-hidden="true"><circle cx="12" cy="12" r="8.5"/><path d="M9.6 9.4a2.5 2.5 0 1 1 3.4 3l-1 .8v1.3M12 17.5v.01"/></svg>Help &amp; docs
          </a>
          <a href="#invite" style="--g:2" class="relative flex items-center gap-3 min-h-11 pl-3.5 pr-2.5 rounded-[10px] no-underline text-[13.5px] font-semibold text-thb12-dmut opacity-0 -translate-x-3.5 transition-[background,color,opacity,translate] duration-350 ease-thb12 hover:bg-[oklch(1_0_0/0.06)] hover:text-thb12-dink focus-visible:text-thb12-dink focus-visible:outline-2 focus-visible:outline-thb12-acc focus-visible:-outline-offset-2 group-data-open:opacity-100 group-data-open:translate-x-0 group-data-open:[transition-delay:calc(100ms+var(--g)*60ms)] motion-reduce:translate-x-0 motion-reduce:duration-150">
            <svg viewBox="0 0 24 24" class="w-4.5 h-4.5 shrink-0 fill-none stroke-current stroke-[1.7] [stroke-linecap:round] [stroke-linejoin:round]" aria-hidden="true"><circle cx="10" cy="8.5" r="3.5"/><path d="M3.5 20a6.5 6.5 0 0 1 13 0M18 8v6M15 11h6"/></svg>Invite team
          </a>
        </div>
      </nav>
      <footer class="flex items-center gap-2.75 px-3.5 py-3.25 border-t border-thb12-dedge">
        <span class="grid place-items-center w-8.5 h-8.5 rounded-full bg-linear-[140deg,oklch(0.75_0.12_90),var(--color-thb12-acc)] text-[11.5px] font-bold text-thb12-dark" aria-hidden="true">MK</span>
        <span class="grid leading-tight">
          <b class="text-[13px] font-bold">Mara Kade</b>
          <small class="text-[11px] text-thb12-dmut">mara@tessera.app</small>
        </span>
        <button type="button" aria-label="Account options" class="ml-auto w-9 h-9 rounded-[9px] border-0 bg-transparent text-thb12-dmut text-base cursor-pointer transition-[background,color] duration-200 hover:bg-[oklch(1_0_0/0.07)] hover:text-thb12-dink focus-visible:outline-2 focus-visible:outline-thb12-acc focus-visible:outline-offset-2">&ctdot;</button>
      </footer>
    </aside>
  </div>
</section>
@import "tailwindcss";

@theme {
  --color-thb12-bg: #fafafc;
  --color-thb12-bg: oklch(0.975 0.004 250);
  --color-thb12-edge: #e3e4ea;
  --color-thb12-edge: oklch(0.9 0.008 250);
  --color-thb12-line: #e8e9ee;
  --color-thb12-line: oklch(0.91 0.006 250);
  --color-thb12-hover: #eff0f4;
  --color-thb12-hover: oklch(0.95 0.006 250);
  --color-thb12-ink: #33374a;
  --color-thb12-ink: oklch(0.25 0.015 255);
  --color-thb12-mut: #757a8b;
  --color-thb12-mut: oklch(0.55 0.012 255);
  --color-thb12-dark: #2a2d3d;
  --color-thb12-dark: oklch(0.21 0.022 262);
  --color-thb12-dedge: #43475c;
  --color-thb12-dedge: oklch(0.3 0.025 262);
  --color-thb12-dedge2: #62677f;
  --color-thb12-dedge2: oklch(0.42 0.03 262);
  --color-thb12-dink: #eaebf1;
  --color-thb12-dink: oklch(0.93 0.008 260);
  --color-thb12-dmut: #a1a5b6;
  --color-thb12-dmut: oklch(0.66 0.02 260);
  --color-thb12-dcap: #737890;
  --color-thb12-dcap: oklch(0.52 0.02 262);
  --color-thb12-acc: #4ecfc0;
  --color-thb12-acc: oklch(0.73 0.14 175);
  --font-thb12-sans: 'Space Grotesk', system-ui, sans-serif;
  --font-thb12-mono: 'JetBrains Mono', ui-monospace, monospace;
  --ease-thb12: cubic-bezier(0.32, 0.72, 0.28, 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-12 {
  width: 100%;
  min-height: 100svh;
}
(function () {
  document.querySelectorAll('.thb-12 > .group').forEach(function (stage) {
    if (stage.dataset.bound) return; stage.dataset.bound = '1';
    var btn = stage.querySelector('button[aria-controls="thb12-drawer"]'),
        scrim = stage.querySelector('button[aria-hidden="true"]'),
        close = stage.querySelector('button[aria-label="Close navigation"]');
    function set(open) {
      stage.toggleAttribute('data-open', open);
      btn.setAttribute('aria-expanded', String(open));
      if (open) close.focus(); else btn.focus();
    }
    btn.addEventListener('click', function () { set(true); });
    scrim.addEventListener('click', function () { set(false); });
    close.addEventListener('click', function () { set(false); });
    stage.addEventListener('keydown', function (e) {
      if (e.key === 'Escape' && stage.hasAttribute('data-open')) set(false);
    });
  });
})();

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-thb12-bg: #fafafc;
  --color-thb12-bg: oklch(0.975 0.004 250);
  --color-thb12-edge: #e3e4ea;
  --color-thb12-edge: oklch(0.9 0.008 250);
  --color-thb12-line: #e8e9ee;
  --color-thb12-line: oklch(0.91 0.006 250);
  --color-thb12-hover: #eff0f4;
  --color-thb12-hover: oklch(0.95 0.006 250);
  --color-thb12-ink: #33374a;
  --color-thb12-ink: oklch(0.25 0.015 255);
  --color-thb12-mut: #757a8b;
  --color-thb12-mut: oklch(0.55 0.012 255);
  --color-thb12-dark: #2a2d3d;
  --color-thb12-dark: oklch(0.21 0.022 262);
  --color-thb12-dedge: #43475c;
  --color-thb12-dedge: oklch(0.3 0.025 262);
  --color-thb12-dedge2: #62677f;
  --color-thb12-dedge2: oklch(0.42 0.03 262);
  --color-thb12-dink: #eaebf1;
  --color-thb12-dink: oklch(0.93 0.008 260);
  --color-thb12-dmut: #a1a5b6;
  --color-thb12-dmut: oklch(0.66 0.02 260);
  --color-thb12-dcap: #737890;
  --color-thb12-dcap: oklch(0.52 0.02 262);
  --color-thb12-acc: #4ecfc0;
  --color-thb12-acc: oklch(0.73 0.14 175);
  --font-thb12-sans: 'Space Grotesk', system-ui, sans-serif;
  --font-thb12-mono: 'JetBrains Mono', ui-monospace, monospace;
  --ease-thb12: cubic-bezier(0.32, 0.72, 0.28, 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-12 {
  width: 100%;
  min-height: 100svh;
}
3Fonts used by this demooptional — skip if you have your own type stack
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap">

What changed in the port

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

  • a[aria-current=&quot;page&quot;] and its three dependent rules (tint, indicator, icon colour) collapse into aria-current: utilities on the row and its children. The active-state contract is now visible where the row is written.
  • The indicator moves from a ::before to a real aria-hidden span, which is what lets it carry aria-current:scale-y-100 directly — a pseudo-element cannot read an attribute variant of its own parent without a nested selector.
  • The per-group cascade keeps --g but drops the four-value transition-delay list; one [transition-delay:calc(…)] utility does it.
  • .group + .group{margin-top} has no variant form, so it becomes [&+div]:mt-4.5 on each group — the clearest case in the collection of an adjacent-sibling rule staying awkward in utilities.
  • The dark drawer inside a light stage means two token families in one @theme block (thb12-* and thb12-d*). Utilities do not inherit a palette, so naming carries the distinction.
  • The burger's three unequal bars growing to equal width on hover uses a named group (group/btn), keeping it independent of the stage's group.
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: Tailwind Slide Out Sidebar Navigation / Off Canvas Menu
Source: https://codefronts.com/navigation/tailwind-hamburger-menus/tailwind-slide-out-sidebar-navigation-off-canvas-menu/

The workhorse app sidebar, structured the way real products need it: grouped sections, icon+label rows with an aria-current-driven indicator bar, shortcut hints, and a user card pinned to the bottom. The slide engine is demo 04's; what this demo teaches is interior architecture &mdash; and in utilities the active-state contract (aria-current:) is visible on the row itself.
## HTML
```html
<section class="thb-12 w-full min-h-svh grid justify-center content-start bg-thb12-bg font-thb12-sans text-thb12-ink px-5 py-9" aria-label="Slide out sidebar navigation demo">
  <div class="group relative w-[min(420px,88vw)] h-140 rounded-3xl overflow-hidden bg-thb12-bg border border-thb12-edge" style="--w:288px">
    <header class="flex items-center gap-3 h-16 px-4 bg-white border-b border-thb12-edge">
      <button type="button" aria-expanded="false" aria-controls="thb12-drawer" aria-label="Open navigation" class="group/btn grid place-items-center w-11 h-11 rounded-xl border-0 bg-transparent text-thb12-ink cursor-pointer transition-[background] duration-200 hover:bg-thb12-hover focus-visible:outline-2 focus-visible:outline-thb12-acc focus-visible:outline-offset-2">
        <span class="relative block w-4.75 h-3.25" aria-hidden="true">
          <i class="absolute left-0 top-0 w-4.75 h-0.5 rounded-sm bg-current transition-[width] duration-250"></i>
          <i class="absolute left-0 top-[5.5px] w-3.25 h-0.5 rounded-sm bg-current transition-[width] duration-250 group-hover/btn:w-4.75"></i>
          <i class="absolute left-0 top-[11px] w-4 h-0.5 rounded-sm bg-current transition-[width] duration-250 group-hover/btn:w-4.75"></i>
        </span>
      </button>
      <strong class="text-[15px] font-bold tracking-[-0.01em]">Tessera</strong>
      <span class="text-[13px] text-thb12-mut" aria-hidden="true">/ Q3 planning</span>
    </header>
    <div class="grid gap-3.25 p-4.5" aria-hidden="true">
      <div class="h-40 rounded-[15px] bg-linear-[145deg,oklch(0.92_0.02_190),oklch(0.88_0.035_165)]"></div>
      <div class="flex gap-3">
        <span class="flex-1 h-19.5 rounded-[13px] bg-white border border-thb12-edge"></span>
        <span class="flex-1 h-19.5 rounded-[13px] bg-white border border-thb12-edge"></span>
      </div>
      <div class="h-2.75 w-[78%] rounded-full bg-thb12-line"></div>
      <div class="h-2.75 w-[62%] rounded-full bg-thb12-line"></div>
    </div>
    <button type="button" tabindex="-1" aria-hidden="true" class="absolute inset-0 z-2 border-0 p-0 cursor-pointer opacity-0 invisible bg-[oklch(0.2_0.02_260/0.4)] [transition:opacity_.32s,visibility_0s_.32s] group-data-open:opacity-100 group-data-open:visible group-data-open:[transition:opacity_.32s,visibility_0s_0s]"></button>
    <aside id="thb12-drawer" aria-label="Navigation" class="absolute inset-y-0 left-0 z-3 grid grid-rows-[auto_1fr_auto] w-[min(88%,var(--w))] bg-thb12-dark text-thb12-dink shadow-[24px_0_48px_-22px_oklch(0.15_0.03_262/0.6)] -translate-x-[102%] invisible [transition:translate_.42s_var(--ease-thb12),visibility_0s_.42s] group-data-open:translate-x-0 group-data-open:visible group-data-open:[transition:translate_.42s_var(--ease-thb12),visibility_0s_0s] motion-reduce:[transition:opacity_.15s,visibility_0s_.15s]">
      <header class="flex items-center gap-2.5 pt-4.5 pb-3.5 pl-4.5 pr-3.5">
        <span class="grid place-items-center w-7 h-7 rounded-[9px] bg-linear-[140deg,var(--color-thb12-acc),oklch(0.6_0.12_210)] text-[13px] font-extrabold text-thb12-dark" aria-hidden="true">T</span>
        <b class="text-[15px] font-bold">Tessera</b>
        <button type="button" aria-label="Close navigation" class="grid place-items-center ml-auto w-9.5 h-9.5 rounded-[10px] border border-thb12-dedge bg-transparent text-thb12-dmut cursor-pointer transition-[color,border-color] duration-200 hover:text-thb12-dink hover:border-thb12-dedge2 focus-visible:outline-2 focus-visible:outline-thb12-acc focus-visible:outline-offset-2">
          <svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true"><path d="m6 6 12 12M18 6 6 18"/></svg>
        </button>
      </header>
      <nav aria-label="Primary" class="min-h-0 overflow-auto overscroll-contain px-3 pt-1.5 pb-3.5">
        <div style="--g:0" class="[&+div]:mt-4.5">
          <span class="block px-2.5 pb-1.75 font-thb12-mono text-[10px] font-bold tracking-[0.14em] uppercase text-thb12-dcap">Workspace</span>
          <a href="#board" aria-current="page" style="--g:0" class="relative flex items-center gap-3 min-h-11 pl-3.5 pr-2.5 rounded-[10px] no-underline text-[13.5px] font-semibold text-thb12-dmut opacity-0 -translate-x-3.5 transition-[background,color,opacity,translate] duration-350 ease-thb12 hover:bg-[oklch(1_0_0/0.06)] hover:text-thb12-dink focus-visible:text-thb12-dink focus-visible:outline-2 focus-visible:outline-thb12-acc focus-visible:-outline-offset-2 aria-current:text-thb12-dink aria-current:bg-[oklch(1_0_0/0.07)] group-data-open:opacity-100 group-data-open:translate-x-0 group-data-open:[transition-delay:calc(100ms+var(--g)*60ms)] motion-reduce:translate-x-0 motion-reduce:duration-150">
            <span class="absolute left-0.5 top-[20%] w-[3.5px] h-[60%] rounded-full bg-thb12-acc scale-y-0 transition-[scale] duration-250 aria-current:scale-y-100" aria-hidden="true"></span>
            <svg viewBox="0 0 24 24" class="w-4.5 h-4.5 shrink-0 fill-none stroke-current stroke-[1.7] [stroke-linecap:round] [stroke-linejoin:round] aria-current:stroke-thb12-acc" aria-hidden="true"><rect x="3.5" y="3.5" width="7.5" height="10" rx="1.5"/><rect x="13" y="3.5" width="7.5" height="6" rx="1.5"/><rect x="13" y="11.5" width="7.5" height="9" rx="1.5"/><rect x="3.5" y="15.5" width="7.5" height="5" rx="1.5"/></svg>Board
            <kbd class="ml-auto px-1.5 py-0.5 rounded-[5px] border border-thb12-dedge font-thb12-mono text-[10px] font-semibold text-thb12-dcap" aria-hidden="true">&#8984;1</kbd>
          </a>
          <a href="#timeline" style="--g:0" class="relative flex items-center gap-3 min-h-11 pl-3.5 pr-2.5 rounded-[10px] no-underline text-[13.5px] font-semibold text-thb12-dmut opacity-0 -translate-x-3.5 transition-[background,color,opacity,translate] duration-350 ease-thb12 hover:bg-[oklch(1_0_0/0.06)] hover:text-thb12-dink focus-visible:text-thb12-dink focus-visible:outline-2 focus-visible:outline-thb12-acc focus-visible:-outline-offset-2 group-data-open:opacity-100 group-data-open:translate-x-0 group-data-open:[transition-delay:calc(100ms+var(--g)*60ms)] motion-reduce:translate-x-0 motion-reduce:duration-150">
            <svg viewBox="0 0 24 24" class="w-4.5 h-4.5 shrink-0 fill-none stroke-current stroke-[1.7] [stroke-linecap:round] [stroke-linejoin:round]" aria-hidden="true"><path d="M3.5 6h11M3.5 12h17M3.5 18h8"/><circle cx="18" cy="6" r="2"/><circle cx="15" cy="18" r="2"/></svg>Timeline
            <kbd class="ml-auto px-1.5 py-0.5 rounded-[5px] border border-thb12-dedge font-thb12-mono text-[10px] font-semibold text-thb12-dcap" aria-hidden="true">&#8984;2</kbd>
          </a>
          <a href="#backlog" style="--g:0" class="relative flex items-center gap-3 min-h-11 pl-3.5 pr-2.5 rounded-[10px] no-underline text-[13.5px] font-semibold text-thb12-dmut opacity-0 -translate-x-3.5 transition-[background,color,opacity,translate] duration-350 ease-thb12 hover:bg-[oklch(1_0_0/0.06)] hover:text-thb12-dink focus-visible:text-thb12-dink focus-visible:outline-2 focus-visible:outline-thb12-acc focus-visible:-outline-offset-2 group-data-open:opacity-100 group-data-open:translate-x-0 group-data-open:[transition-delay:calc(100ms+var(--g)*60ms)] motion-reduce:translate-x-0 motion-reduce:duration-150">
            <svg viewBox="0 0 24 24" class="w-4.5 h-4.5 shrink-0 fill-none stroke-current stroke-[1.7] [stroke-linecap:round] [stroke-linejoin:round]" aria-hidden="true"><path d="M4 6h16M4 12h16M4 18h16"/><circle cx="4" cy="6" r="1.4"/><circle cx="4" cy="12" r="1.4"/><circle cx="4" cy="18" r="1.4"/></svg>Backlog
            <b class="ml-auto px-1.75 py-0.5 rounded-full bg-thb12-acc text-thb12-dark text-[10.5px] font-bold">24</b>
          </a>
        </div>
        <div style="--g:1" class="[&+div]:mt-4.5">
          <span class="block px-2.5 pb-1.75 font-thb12-mono text-[10px] font-bold tracking-[0.14em] uppercase text-thb12-dcap">Library</span>
          <a href="#docs" style="--g:1" class="relative flex items-center gap-3 min-h-11 pl-3.5 pr-2.5 rounded-[10px] no-underline text-[13.5px] font-semibold text-thb12-dmut opacity-0 -translate-x-3.5 transition-[background,color,opacity,translate] duration-350 ease-thb12 hover:bg-[oklch(1_0_0/0.06)] hover:text-thb12-dink focus-visible:text-thb12-dink focus-visible:outline-2 focus-visible:outline-thb12-acc focus-visible:-outline-offset-2 group-data-open:opacity-100 group-data-open:translate-x-0 group-data-open:[transition-delay:calc(100ms+var(--g)*60ms)] motion-reduce:translate-x-0 motion-reduce:duration-150">
            <svg viewBox="0 0 24 24" class="w-4.5 h-4.5 shrink-0 fill-none stroke-current stroke-[1.7] [stroke-linecap:round] [stroke-linejoin:round]" aria-hidden="true"><path d="M5.5 4h9l4 4v12h-13Z"/><path d="M14 4v4.5h4.5"/></svg>Documents
          </a>
          <a href="#assets" style="--g:1" class="relative flex items-center gap-3 min-h-11 pl-3.5 pr-2.5 rounded-[10px] no-underline text-[13.5px] font-semibold text-thb12-dmut opacity-0 -translate-x-3.5 transition-[background,color,opacity,translate] duration-350 ease-thb12 hover:bg-[oklch(1_0_0/0.06)] hover:text-thb12-dink focus-visible:text-thb12-dink focus-visible:outline-2 focus-visible:outline-thb12-acc focus-visible:-outline-offset-2 group-data-open:opacity-100 group-data-open:translate-x-0 group-data-open:[transition-delay:calc(100ms+var(--g)*60ms)] motion-reduce:translate-x-0 motion-reduce:duration-150">
            <svg viewBox="0 0 24 24" class="w-4.5 h-4.5 shrink-0 fill-none stroke-current stroke-[1.7] [stroke-linecap:round] [stroke-linejoin:round]" aria-hidden="true"><rect x="3.5" y="5" width="17" height="14" rx="2"/><circle cx="9" cy="10.5" r="1.8"/><path d="m3.5 16.5 5-4 4 3.5 3-2.5 5 3.5"/></svg>Assets
          </a>
          <a href="#reports" style="--g:1" class="relative flex items-center gap-3 min-h-11 pl-3.5 pr-2.5 rounded-[10px] no-underline text-[13.5px] font-semibold text-thb12-dmut opacity-0 -translate-x-3.5 transition-[background,color,opacity,translate] duration-350 ease-thb12 hover:bg-[oklch(1_0_0/0.06)] hover:text-thb12-dink focus-visible:text-thb12-dink focus-visible:outline-2 focus-visible:outline-thb12-acc focus-visible:-outline-offset-2 group-data-open:opacity-100 group-data-open:translate-x-0 group-data-open:[transition-delay:calc(100ms+var(--g)*60ms)] motion-reduce:translate-x-0 motion-reduce:duration-150">
            <svg viewBox="0 0 24 24" class="w-4.5 h-4.5 shrink-0 fill-none stroke-current stroke-[1.7] [stroke-linecap:round] [stroke-linejoin:round]" aria-hidden="true"><path d="M5 20V9M12 20V4M19 20v-7"/></svg>Reports
          </a>
        </div>
        <div style="--g:2">
          <span class="block px-2.5 pb-1.75 font-thb12-mono text-[10px] font-bold tracking-[0.14em] uppercase text-thb12-dcap">Support</span>
          <a href="#help" style="--g:2" class="relative flex items-center gap-3 min-h-11 pl-3.5 pr-2.5 rounded-[10px] no-underline text-[13.5px] font-semibold text-thb12-dmut opacity-0 -translate-x-3.5 transition-[background,color,opacity,translate] duration-350 ease-thb12 hover:bg-[oklch(1_0_0/0.06)] hover:text-thb12-dink focus-visible:text-thb12-dink focus-visible:outline-2 focus-visible:outline-thb12-acc focus-visible:-outline-offset-2 group-data-open:opacity-100 group-data-open:translate-x-0 group-data-open:[transition-delay:calc(100ms+var(--g)*60ms)] motion-reduce:translate-x-0 motion-reduce:duration-150">
            <svg viewBox="0 0 24 24" class="w-4.5 h-4.5 shrink-0 fill-none stroke-current stroke-[1.7] [stroke-linecap:round] [stroke-linejoin:round]" aria-hidden="true"><circle cx="12" cy="12" r="8.5"/><path d="M9.6 9.4a2.5 2.5 0 1 1 3.4 3l-1 .8v1.3M12 17.5v.01"/></svg>Help &amp; docs
          </a>
          <a href="#invite" style="--g:2" class="relative flex items-center gap-3 min-h-11 pl-3.5 pr-2.5 rounded-[10px] no-underline text-[13.5px] font-semibold text-thb12-dmut opacity-0 -translate-x-3.5 transition-[background,color,opacity,translate] duration-350 ease-thb12 hover:bg-[oklch(1_0_0/0.06)] hover:text-thb12-dink focus-visible:text-thb12-dink focus-visible:outline-2 focus-visible:outline-thb12-acc focus-visible:-outline-offset-2 group-data-open:opacity-100 group-data-open:translate-x-0 group-data-open:[transition-delay:calc(100ms+var(--g)*60ms)] motion-reduce:translate-x-0 motion-reduce:duration-150">
            <svg viewBox="0 0 24 24" class="w-4.5 h-4.5 shrink-0 fill-none stroke-current stroke-[1.7] [stroke-linecap:round] [stroke-linejoin:round]" aria-hidden="true"><circle cx="10" cy="8.5" r="3.5"/><path d="M3.5 20a6.5 6.5 0 0 1 13 0M18 8v6M15 11h6"/></svg>Invite team
          </a>
        </div>
      </nav>
      <footer class="flex items-center gap-2.75 px-3.5 py-3.25 border-t border-thb12-dedge">
        <span class="grid place-items-center w-8.5 h-8.5 rounded-full bg-linear-[140deg,oklch(0.75_0.12_90),var(--color-thb12-acc)] text-[11.5px] font-bold text-thb12-dark" aria-hidden="true">MK</span>
        <span class="grid leading-tight">
          <b class="text-[13px] font-bold">Mara Kade</b>
          <small class="text-[11px] text-thb12-dmut">mara@tessera.app</small>
        </span>
        <button type="button" aria-label="Account options" class="ml-auto w-9 h-9 rounded-[9px] border-0 bg-transparent text-thb12-dmut text-base cursor-pointer transition-[background,color] duration-200 hover:bg-[oklch(1_0_0/0.07)] hover:text-thb12-dink focus-visible:outline-2 focus-visible:outline-thb12-acc focus-visible:outline-offset-2">&ctdot;</button>
      </footer>
    </aside>
  </div>
</section>
```
## CSS
```css
@import "tailwindcss";

@theme {
  --color-thb12-bg: #fafafc;
  --color-thb12-bg: oklch(0.975 0.004 250);
  --color-thb12-edge: #e3e4ea;
  --color-thb12-edge: oklch(0.9 0.008 250);
  --color-thb12-line: #e8e9ee;
  --color-thb12-line: oklch(0.91 0.006 250);
  --color-thb12-hover: #eff0f4;
  --color-thb12-hover: oklch(0.95 0.006 250);
  --color-thb12-ink: #33374a;
  --color-thb12-ink: oklch(0.25 0.015 255);
  --color-thb12-mut: #757a8b;
  --color-thb12-mut: oklch(0.55 0.012 255);
  --color-thb12-dark: #2a2d3d;
  --color-thb12-dark: oklch(0.21 0.022 262);
  --color-thb12-dedge: #43475c;
  --color-thb12-dedge: oklch(0.3 0.025 262);
  --color-thb12-dedge2: #62677f;
  --color-thb12-dedge2: oklch(0.42 0.03 262);
  --color-thb12-dink: #eaebf1;
  --color-thb12-dink: oklch(0.93 0.008 260);
  --color-thb12-dmut: #a1a5b6;
  --color-thb12-dmut: oklch(0.66 0.02 260);
  --color-thb12-dcap: #737890;
  --color-thb12-dcap: oklch(0.52 0.02 262);
  --color-thb12-acc: #4ecfc0;
  --color-thb12-acc: oklch(0.73 0.14 175);
  --font-thb12-sans: 'Space Grotesk', system-ui, sans-serif;
  --font-thb12-mono: 'JetBrains Mono', ui-monospace, monospace;
  --ease-thb12: cubic-bezier(0.32, 0.72, 0.28, 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-12 {
  width: 100%;
  min-height: 100svh;
}
```

## JavaScript
```js
(function () {
  document.querySelectorAll('.thb-12 > .group').forEach(function (stage) {
    if (stage.dataset.bound) return; stage.dataset.bound = '1';
    var btn = stage.querySelector('button[aria-controls="thb12-drawer"]'),
        scrim = stage.querySelector('button[aria-hidden="true"]'),
        close = stage.querySelector('button[aria-label="Close navigation"]');
    function set(open) {
      stage.toggleAttribute('data-open', open);
      btn.setAttribute('aria-expanded', String(open));
      if (open) close.focus(); else btn.focus();
    }
    btn.addEventListener('click', function () { set(true); });
    scrim.addEventListener('click', function () { set(false); });
    close.addEventListener('click', function () { set(false); });
    stage.addEventListener('keydown', function (e) {
      if (e.key === 'Escape' && stage.hasAttribute('data-open')) set(false);
    });
  });
})();
```

How this works

The active row is an attribute, not a class.

<a href="#board" aria-current="page"
   class="text-thb12-dmut aria-current:text-thb12-dink
          aria-current:bg-[oklch(1_0_0/0.07)]">
  <span class="scale-y-0 aria-current:scale-y-100 …"></span>
  <svg class="aria-current:stroke-thb12-acc …">…</svg>Board
</a>

Move the attribute and the indicator, the tint and the icon colour all follow. No class shuffling, and no possibility of the highlight disagreeing with what a screen reader announces.

Three structural decisions a drawer needs up front. A three-row grid so only the middle scrolls; min-h-0 so that middle row can actually shrink; and rhythm between groups so sections stay readable as the product grows. All three are utilities on the elements themselves.

The cascade is per group, not per row. Rows inside a section share their parent's --g, so a section arrives as a unit — cheaper to author than per-row indices and visually calmer.

Shortcut hints are <kbd> and aria-hidden. Visual users get density; screen-reader users are not told “Projects, command 2” as if the link name were longer. That asymmetry is correct, not lazy.

Make it yours

  • Add a section: copy one group <div> and give it the next --g. The cascade, scrolling and spacing are automatic.
  • Active page: move aria-current=&quot;page&quot;. Nothing else changes.
  • Width: w-[min(88%,var(--w))] with --w:288px. Under 260px real product names truncate; over 320px steals canvas on 13-inch laptops.
  • The ellipsis button in the user card is a real button already wired for focus — mount your account menu there.
  • Retheme the drawer through the thb12-d* tokens; the stage keeps its own light set.

Gotchas — read before shipping

  • The scrim and drawer must be SIBLINGS. A scrim wrapping the drawer captures the drawer's own clicks and &ldquo;close on outside click&rdquo; becomes &ldquo;close on any click&rdquo;.
  • Only ONE element per nav may carry aria-current=&quot;page&quot;. The variant will happily style several, and screen readers will report a confused page.
  • min-h-0 on the scrolling container, again — without it a long section pushes the pinned user card out of view.
  • The <kbd> hints must stay aria-hidden, or they are read as part of each link's name.
  • scale-y-0 needs a transform origin to grow from the middle as intended; the default centre is right here, but a top-anchored indicator needs origin-top.
  • Adjacent-sibling spacing has no variant. [&+div]:mt-4.5 is the honest form — do not fake it with a wrapper gap unless every group is the same shape.

Browser support

ChromeSafariFirefoxEdge
111+16.4+128+111+

Nothing exotic: grid rows, translate transitions and attribute selectors. The floor quoted here is Tailwind v4's own generated sheet.

Techniques used in this demo

Theme tokensset in @theme, used by a utility

HTML & JSno Tailwind involved

Search CodeFronts

Loading…