Pure TailwindMIT licensed

Tailwind Collapsible Sidebar with Toggle Button (No JS)

A sidebar that animates between a 240px full layout and a 64px icon rail at the press of a pivot button, with labels, badges and the user card collapsing in sync via max-w-0 opacity-0. The toggle button flips its arrow and its accessible name (“Collapse sidebar” ↔ “Expand sidebar”), and collapsed labels stay in the accessibility tree. Zero JavaScript.

Live Demo
Plain CSSTailwind v4
Try it

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

Untitled UI React · Affiliate link

Get Untitled UI Pro

The code

<section class="tsm-15 group flex w-full min-h-svh bg-tsm15-bg font-tsm15-sans text-tsm15-ink" aria-label="Collapsible sidebar with toggle button demo">
  <input type="checkbox" id="tsm-15-toggle" class="peer sr-only">
  <nav aria-label="Main" class="relative flex w-60 shrink-0 flex-col overflow-hidden border-r border-tsm15-acc/12 bg-tsm15-nav transition-[width] duration-350 ease-[cubic-bezier(0.4,0,0.2,1)] group-has-checked:w-16 motion-reduce:transition-none">
    <div class="mb-3 flex items-center gap-2 border-b border-tsm15-acc/12 px-3.5 py-4 group-has-checked:flex-col group-has-checked:px-0">
      <span aria-hidden="true" class="grid size-9 shrink-0 place-items-center rounded-[9px] bg-linear-135 from-tsm15-acc to-tsm15-acc2 text-base font-extrabold text-white">R</span>
      <span class="max-w-40 flex-1 text-[15px] font-extrabold overflow-hidden whitespace-nowrap transition-[max-width,opacity,margin] duration-350 ease-[cubic-bezier(0.4,0,0.2,1)] group-has-checked:max-w-0 group-has-checked:opacity-0 motion-reduce:transition-none">Recto</span>
      <label for="tsm-15-toggle" data-tsm15-collapse class="grid size-11 shrink-0 cursor-pointer place-items-center rounded-full text-tsm15-mut transition-colors hover:bg-tsm15-acc hover:text-white group-has-[input:focus-visible]:outline-2 group-has-[input:focus-visible]:outline-tsm15-acc2">
        <span class="sr-only group-has-checked:hidden">Collapse sidebar</span><span class="sr-only hidden group-has-checked:inline">Expand sidebar</span>
        <svg class="size-4 transition-transform duration-350 group-has-checked:rotate-180 motion-reduce:transition-none" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m15 6-6 6 6 6"/></svg>
      </label>
    </div>
    <ul class="grid flex-1 content-start gap-1 px-2.5 group-has-checked:px-[7px]">
      <li><a href="#" class="group/l flex min-h-11 items-center overflow-hidden rounded-[10px] px-1.5 text-[13px] font-semibold text-tsm15-mut transition-colors hover:bg-tsm15-acc/8 hover:text-tsm15-ink focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tsm15-acc2 aria-[current=page]:bg-tsm15-acc/10 aria-[current=page]:text-tsm15-acc2" aria-current="page"><span class="grid size-9 shrink-0 place-items-center rounded-lg transition-colors group-hover/l:bg-tsm15-acc/15 group-aria-[current=page]/l:bg-tsm15-acc/18"><svg class="size-[18px] shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="3" width="7" height="7" rx="1.5"/><rect x="14" y="3" width="7" height="7" rx="1.5"/><rect x="3" y="14" width="7" height="7" rx="1.5"/><rect x="14" y="14" width="7" height="7" rx="1.5"/></svg></span><span class="ml-1.5 max-w-40 overflow-hidden whitespace-nowrap transition-[max-width,opacity,margin] duration-350 ease-[cubic-bezier(0.4,0,0.2,1)] group-has-checked:max-w-0 group-has-checked:opacity-0 motion-reduce:transition-none group-has-checked:ml-0">Dashboard</span></a></li>
      <li><a href="#" class="group/l flex min-h-11 items-center overflow-hidden rounded-[10px] px-1.5 text-[13px] font-semibold text-tsm15-mut transition-colors hover:bg-tsm15-acc/8 hover:text-tsm15-ink focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tsm15-acc2 aria-[current=page]:bg-tsm15-acc/10 aria-[current=page]:text-tsm15-acc2"><span class="grid size-9 shrink-0 place-items-center rounded-lg transition-colors group-hover/l:bg-tsm15-acc/15 group-aria-[current=page]/l:bg-tsm15-acc/18"><svg class="size-[18px] shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 3v18h18"/><path d="m7 15 4-4 3 3 5-6"/></svg></span><span class="ml-1.5 max-w-40 overflow-hidden whitespace-nowrap transition-[max-width,opacity,margin] duration-350 ease-[cubic-bezier(0.4,0,0.2,1)] group-has-checked:max-w-0 group-has-checked:opacity-0 motion-reduce:transition-none group-has-checked:ml-0">Analytics</span><span class="ml-auto rounded-full bg-tsm15-acc/20 px-2 py-0.5 text-[10px] font-bold text-tsm15-acc2 transition-opacity group-has-checked:opacity-0">9</span></a></li>
      <li><a href="#" class="group/l flex min-h-11 items-center overflow-hidden rounded-[10px] px-1.5 text-[13px] font-semibold text-tsm15-mut transition-colors hover:bg-tsm15-acc/8 hover:text-tsm15-ink focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tsm15-acc2 aria-[current=page]:bg-tsm15-acc/10 aria-[current=page]:text-tsm15-acc2"><span class="grid size-9 shrink-0 place-items-center rounded-lg transition-colors group-hover/l:bg-tsm15-acc/15 group-aria-[current=page]/l:bg-tsm15-acc/18"><svg class="size-[18px] shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m12 3 8 4.5v9L12 21l-8-4.5v-9z"/><path d="m4 7.5 8 4.5 8-4.5"/><path d="M12 12v9"/></svg></span><span class="ml-1.5 max-w-40 overflow-hidden whitespace-nowrap transition-[max-width,opacity,margin] duration-350 ease-[cubic-bezier(0.4,0,0.2,1)] group-has-checked:max-w-0 group-has-checked:opacity-0 motion-reduce:transition-none group-has-checked:ml-0">Products</span></a></li>
      <li><a href="#" class="group/l flex min-h-11 items-center overflow-hidden rounded-[10px] px-1.5 text-[13px] font-semibold text-tsm15-mut transition-colors hover:bg-tsm15-acc/8 hover:text-tsm15-ink focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tsm15-acc2 aria-[current=page]:bg-tsm15-acc/10 aria-[current=page]:text-tsm15-acc2"><span class="grid size-9 shrink-0 place-items-center rounded-lg transition-colors group-hover/l:bg-tsm15-acc/15 group-aria-[current=page]/l:bg-tsm15-acc/18"><svg class="size-[18px] shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 4h2l2.4 11h11L21 8H6.2"/><circle cx="9" cy="19.5" r="1.5"/><circle cx="17" cy="19.5" r="1.5"/></svg></span><span class="ml-1.5 max-w-40 overflow-hidden whitespace-nowrap transition-[max-width,opacity,margin] duration-350 ease-[cubic-bezier(0.4,0,0.2,1)] group-has-checked:max-w-0 group-has-checked:opacity-0 motion-reduce:transition-none group-has-checked:ml-0">Orders</span><span class="ml-auto rounded-full bg-tsm15-acc/20 px-2 py-0.5 text-[10px] font-bold text-tsm15-acc2 transition-opacity group-has-checked:opacity-0">3</span></a></li>
      <li><a href="#" class="group/l flex min-h-11 items-center overflow-hidden rounded-[10px] px-1.5 text-[13px] font-semibold text-tsm15-mut transition-colors hover:bg-tsm15-acc/8 hover:text-tsm15-ink focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tsm15-acc2 aria-[current=page]:bg-tsm15-acc/10 aria-[current=page]:text-tsm15-acc2"><span class="grid size-9 shrink-0 place-items-center rounded-lg transition-colors group-hover/l:bg-tsm15-acc/15 group-aria-[current=page]/l:bg-tsm15-acc/18"><svg class="size-[18px] shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="9" cy="8" r="3.5"/><path d="M2.5 20a6.5 6.5 0 0 1 13 0"/><path d="M16 4.5a3.5 3.5 0 0 1 0 7"/><path d="M18 14.5a6.5 6.5 0 0 1 3.5 5.5"/></svg></span><span class="ml-1.5 max-w-40 overflow-hidden whitespace-nowrap transition-[max-width,opacity,margin] duration-350 ease-[cubic-bezier(0.4,0,0.2,1)] group-has-checked:max-w-0 group-has-checked:opacity-0 motion-reduce:transition-none group-has-checked:ml-0">Customers</span></a></li>
      <li><a href="#" class="group/l flex min-h-11 items-center overflow-hidden rounded-[10px] px-1.5 text-[13px] font-semibold text-tsm15-mut transition-colors hover:bg-tsm15-acc/8 hover:text-tsm15-ink focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tsm15-acc2 aria-[current=page]:bg-tsm15-acc/10 aria-[current=page]:text-tsm15-acc2"><span class="grid size-9 shrink-0 place-items-center rounded-lg transition-colors group-hover/l:bg-tsm15-acc/15 group-aria-[current=page]/l:bg-tsm15-acc/18"><svg class="size-[18px] shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 6h9"/><path d="M19 6h1"/><circle cx="16" cy="6" r="2.5"/><path d="M4 12h2"/><path d="M12 12h8"/><circle cx="9" cy="12" r="2.5"/><path d="M4 18h11"/><circle cx="18" cy="18" r="2.5"/></svg></span><span class="ml-1.5 max-w-40 overflow-hidden whitespace-nowrap transition-[max-width,opacity,margin] duration-350 ease-[cubic-bezier(0.4,0,0.2,1)] group-has-checked:max-w-0 group-has-checked:opacity-0 motion-reduce:transition-none group-has-checked:ml-0">Settings</span></a></li>
    </ul>
    <div class="flex items-center overflow-hidden border-t border-tsm15-acc/12 px-3 py-4 group-has-checked:justify-center group-has-checked:px-0">
      <span aria-hidden="true" class="grid size-9 shrink-0 place-items-center rounded-full bg-linear-135 from-tsm15-acc to-violet-500 text-xs font-bold text-white">WP</span>
      <span class="ml-2.5 max-w-40 overflow-hidden whitespace-nowrap transition-[max-width,opacity,margin] duration-350 ease-[cubic-bezier(0.4,0,0.2,1)] group-has-checked:max-w-0 group-has-checked:opacity-0 motion-reduce:transition-none group-has-checked:ml-0"><span class="block text-xs font-bold">W. Park</span><span class="block text-[10px] text-tsm15-mut">Admin</span></span>
    </div>
  </nav>
  <div class="min-w-0 flex-1 p-6">
    <div class="mx-auto max-w-3xl">
      <h1 class="text-xl font-extrabold">Dashboard</h1>
      <p class="mt-1.5 mb-6 max-w-prose text-[13px] leading-relaxed text-tsm15-mut text-pretty">Press the arrow beside the logo to collapse the sidebar to icons and back. One hidden checkbox, one width transition.</p>
      <div class="grid grid-cols-2 gap-2.5 md:grid-cols-4">
          <div class="rounded-xl border border-tsm15-acc/12 bg-tsm15-surface p-4"><p class="text-[22px] font-extrabold tabular-nums text-tsm15-acc2">$82k</p><p class="mt-1 text-[11px] text-tsm15-mut">Revenue</p></div>
          <div class="rounded-xl border border-tsm15-acc/12 bg-tsm15-surface p-4"><p class="text-[22px] font-extrabold tabular-nums text-tsm15-acc2">1,247</p><p class="mt-1 text-[11px] text-tsm15-mut">Orders</p></div>
          <div class="rounded-xl border border-tsm15-acc/12 bg-tsm15-surface p-4"><p class="text-[22px] font-extrabold tabular-nums text-tsm15-acc2">98%</p><p class="mt-1 text-[11px] text-tsm15-mut">Uptime</p></div>
          <div class="rounded-xl border border-tsm15-acc/12 bg-tsm15-surface p-4"><p class="text-[22px] font-extrabold tabular-nums text-tsm15-acc2">4.9 / 5</p><p class="mt-1 text-[11px] text-tsm15-mut">Rating</p></div>
      </div>
    </div>
  </div>
</section>
@import "tailwindcss";

@theme {
  --color-tsm15-bg: #09090b;
  --color-tsm15-bg: oklch(0.141 0.004 285.8);
  --color-tsm15-surface: #141418;
  --color-tsm15-surface: oklch(0.193 0.008 285.6);
  --color-tsm15-nav: #0f0f12;
  --color-tsm15-nav: oklch(0.17 0.006 285.7);
  --color-tsm15-acc: #e11d48;
  --color-tsm15-acc: oklch(0.586 0.222 17.6);
  --color-tsm15-acc2: #fb7185;
  --color-tsm15-acc2: oklch(0.719 0.169 13.4);
  --color-tsm15-ink: #f1f5f9;
  --color-tsm15-ink: oklch(0.968 0.007 247.9);
  --color-tsm15-mut: #a1a1aa;
  --color-tsm15-mut: oklch(0.712 0.013 286.1);
  --font-tsm15-sans: 'Sora', system-ui, sans-serif;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's utilities, so the
   stage is sized before the browser build compiles. */

.tsm-15 {
  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-tsm15-bg: #09090b;
  --color-tsm15-bg: oklch(0.141 0.004 285.8);
  --color-tsm15-surface: #141418;
  --color-tsm15-surface: oklch(0.193 0.008 285.6);
  --color-tsm15-nav: #0f0f12;
  --color-tsm15-nav: oklch(0.17 0.006 285.7);
  --color-tsm15-acc: #e11d48;
  --color-tsm15-acc: oklch(0.586 0.222 17.6);
  --color-tsm15-acc2: #fb7185;
  --color-tsm15-acc2: oklch(0.719 0.169 13.4);
  --color-tsm15-ink: #f1f5f9;
  --color-tsm15-ink: oklch(0.968 0.007 247.9);
  --color-tsm15-mut: #a1a1aa;
  --color-tsm15-mut: oklch(0.712 0.013 286.1);
  --font-tsm15-sans: 'Sora', system-ui, sans-serif;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's utilities, so the
   stage is sized before the browser build compiles. */

.tsm-15 {
  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=Sora:wght@400;600;700;800&display=swap">

What changed in the port

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

  • Nine .toggle:checked ~ .layout … rules became group-has-checked: utilities, and the .layout wrapper that existed only to make the sibling selector reach the nav was deleted.
  • UI upgrade from the CSS original: the collapse button moved beside the logo (it floated mid-sidebar between links and footer), grew from 28px to 44px, and announces “Collapse sidebar” / “Expand sidebar” to match its state.
  • UI upgrade: the toggle is keyboard-operable with a visible ring (group-has-[input:focus-visible]:) — the original used display:none on the checkbox.
  • UI upgrade: muted grey went from #52525b (2.5:1) to #a1a1aa (7.8:1), and “4.9★” became “4.9 / 5” so the rating reads correctly aloud.
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 Collapsible Sidebar with Toggle Button (No JS)
Source: https://codefronts.com/navigation/tailwind-css-side-menus/tailwind-collapsible-sidebar-toggle-button/

A sidebar that animates between a 240px full layout and a 64px icon rail at the press of a pivot button, with labels, badges and the user card collapsing in sync via max-w-0 opacity-0. The toggle button flips its arrow and its accessible name (“Collapse sidebar” ↔ “Expand sidebar”), and collapsed labels stay in the accessibility tree. Zero JavaScript.
## HTML
```html
<section class="tsm-15 group flex w-full min-h-svh bg-tsm15-bg font-tsm15-sans text-tsm15-ink" aria-label="Collapsible sidebar with toggle button demo">
  <input type="checkbox" id="tsm-15-toggle" class="peer sr-only">
  <nav aria-label="Main" class="relative flex w-60 shrink-0 flex-col overflow-hidden border-r border-tsm15-acc/12 bg-tsm15-nav transition-[width] duration-350 ease-[cubic-bezier(0.4,0,0.2,1)] group-has-checked:w-16 motion-reduce:transition-none">
    <div class="mb-3 flex items-center gap-2 border-b border-tsm15-acc/12 px-3.5 py-4 group-has-checked:flex-col group-has-checked:px-0">
      <span aria-hidden="true" class="grid size-9 shrink-0 place-items-center rounded-[9px] bg-linear-135 from-tsm15-acc to-tsm15-acc2 text-base font-extrabold text-white">R</span>
      <span class="max-w-40 flex-1 text-[15px] font-extrabold overflow-hidden whitespace-nowrap transition-[max-width,opacity,margin] duration-350 ease-[cubic-bezier(0.4,0,0.2,1)] group-has-checked:max-w-0 group-has-checked:opacity-0 motion-reduce:transition-none">Recto</span>
      <label for="tsm-15-toggle" data-tsm15-collapse class="grid size-11 shrink-0 cursor-pointer place-items-center rounded-full text-tsm15-mut transition-colors hover:bg-tsm15-acc hover:text-white group-has-[input:focus-visible]:outline-2 group-has-[input:focus-visible]:outline-tsm15-acc2">
        <span class="sr-only group-has-checked:hidden">Collapse sidebar</span><span class="sr-only hidden group-has-checked:inline">Expand sidebar</span>
        <svg class="size-4 transition-transform duration-350 group-has-checked:rotate-180 motion-reduce:transition-none" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m15 6-6 6 6 6"/></svg>
      </label>
    </div>
    <ul class="grid flex-1 content-start gap-1 px-2.5 group-has-checked:px-[7px]">
      <li><a href="#" class="group/l flex min-h-11 items-center overflow-hidden rounded-[10px] px-1.5 text-[13px] font-semibold text-tsm15-mut transition-colors hover:bg-tsm15-acc/8 hover:text-tsm15-ink focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tsm15-acc2 aria-[current=page]:bg-tsm15-acc/10 aria-[current=page]:text-tsm15-acc2" aria-current="page"><span class="grid size-9 shrink-0 place-items-center rounded-lg transition-colors group-hover/l:bg-tsm15-acc/15 group-aria-[current=page]/l:bg-tsm15-acc/18"><svg class="size-[18px] shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="3" width="7" height="7" rx="1.5"/><rect x="14" y="3" width="7" height="7" rx="1.5"/><rect x="3" y="14" width="7" height="7" rx="1.5"/><rect x="14" y="14" width="7" height="7" rx="1.5"/></svg></span><span class="ml-1.5 max-w-40 overflow-hidden whitespace-nowrap transition-[max-width,opacity,margin] duration-350 ease-[cubic-bezier(0.4,0,0.2,1)] group-has-checked:max-w-0 group-has-checked:opacity-0 motion-reduce:transition-none group-has-checked:ml-0">Dashboard</span></a></li>
      <li><a href="#" class="group/l flex min-h-11 items-center overflow-hidden rounded-[10px] px-1.5 text-[13px] font-semibold text-tsm15-mut transition-colors hover:bg-tsm15-acc/8 hover:text-tsm15-ink focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tsm15-acc2 aria-[current=page]:bg-tsm15-acc/10 aria-[current=page]:text-tsm15-acc2"><span class="grid size-9 shrink-0 place-items-center rounded-lg transition-colors group-hover/l:bg-tsm15-acc/15 group-aria-[current=page]/l:bg-tsm15-acc/18"><svg class="size-[18px] shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 3v18h18"/><path d="m7 15 4-4 3 3 5-6"/></svg></span><span class="ml-1.5 max-w-40 overflow-hidden whitespace-nowrap transition-[max-width,opacity,margin] duration-350 ease-[cubic-bezier(0.4,0,0.2,1)] group-has-checked:max-w-0 group-has-checked:opacity-0 motion-reduce:transition-none group-has-checked:ml-0">Analytics</span><span class="ml-auto rounded-full bg-tsm15-acc/20 px-2 py-0.5 text-[10px] font-bold text-tsm15-acc2 transition-opacity group-has-checked:opacity-0">9</span></a></li>
      <li><a href="#" class="group/l flex min-h-11 items-center overflow-hidden rounded-[10px] px-1.5 text-[13px] font-semibold text-tsm15-mut transition-colors hover:bg-tsm15-acc/8 hover:text-tsm15-ink focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tsm15-acc2 aria-[current=page]:bg-tsm15-acc/10 aria-[current=page]:text-tsm15-acc2"><span class="grid size-9 shrink-0 place-items-center rounded-lg transition-colors group-hover/l:bg-tsm15-acc/15 group-aria-[current=page]/l:bg-tsm15-acc/18"><svg class="size-[18px] shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m12 3 8 4.5v9L12 21l-8-4.5v-9z"/><path d="m4 7.5 8 4.5 8-4.5"/><path d="M12 12v9"/></svg></span><span class="ml-1.5 max-w-40 overflow-hidden whitespace-nowrap transition-[max-width,opacity,margin] duration-350 ease-[cubic-bezier(0.4,0,0.2,1)] group-has-checked:max-w-0 group-has-checked:opacity-0 motion-reduce:transition-none group-has-checked:ml-0">Products</span></a></li>
      <li><a href="#" class="group/l flex min-h-11 items-center overflow-hidden rounded-[10px] px-1.5 text-[13px] font-semibold text-tsm15-mut transition-colors hover:bg-tsm15-acc/8 hover:text-tsm15-ink focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tsm15-acc2 aria-[current=page]:bg-tsm15-acc/10 aria-[current=page]:text-tsm15-acc2"><span class="grid size-9 shrink-0 place-items-center rounded-lg transition-colors group-hover/l:bg-tsm15-acc/15 group-aria-[current=page]/l:bg-tsm15-acc/18"><svg class="size-[18px] shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 4h2l2.4 11h11L21 8H6.2"/><circle cx="9" cy="19.5" r="1.5"/><circle cx="17" cy="19.5" r="1.5"/></svg></span><span class="ml-1.5 max-w-40 overflow-hidden whitespace-nowrap transition-[max-width,opacity,margin] duration-350 ease-[cubic-bezier(0.4,0,0.2,1)] group-has-checked:max-w-0 group-has-checked:opacity-0 motion-reduce:transition-none group-has-checked:ml-0">Orders</span><span class="ml-auto rounded-full bg-tsm15-acc/20 px-2 py-0.5 text-[10px] font-bold text-tsm15-acc2 transition-opacity group-has-checked:opacity-0">3</span></a></li>
      <li><a href="#" class="group/l flex min-h-11 items-center overflow-hidden rounded-[10px] px-1.5 text-[13px] font-semibold text-tsm15-mut transition-colors hover:bg-tsm15-acc/8 hover:text-tsm15-ink focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tsm15-acc2 aria-[current=page]:bg-tsm15-acc/10 aria-[current=page]:text-tsm15-acc2"><span class="grid size-9 shrink-0 place-items-center rounded-lg transition-colors group-hover/l:bg-tsm15-acc/15 group-aria-[current=page]/l:bg-tsm15-acc/18"><svg class="size-[18px] shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="9" cy="8" r="3.5"/><path d="M2.5 20a6.5 6.5 0 0 1 13 0"/><path d="M16 4.5a3.5 3.5 0 0 1 0 7"/><path d="M18 14.5a6.5 6.5 0 0 1 3.5 5.5"/></svg></span><span class="ml-1.5 max-w-40 overflow-hidden whitespace-nowrap transition-[max-width,opacity,margin] duration-350 ease-[cubic-bezier(0.4,0,0.2,1)] group-has-checked:max-w-0 group-has-checked:opacity-0 motion-reduce:transition-none group-has-checked:ml-0">Customers</span></a></li>
      <li><a href="#" class="group/l flex min-h-11 items-center overflow-hidden rounded-[10px] px-1.5 text-[13px] font-semibold text-tsm15-mut transition-colors hover:bg-tsm15-acc/8 hover:text-tsm15-ink focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tsm15-acc2 aria-[current=page]:bg-tsm15-acc/10 aria-[current=page]:text-tsm15-acc2"><span class="grid size-9 shrink-0 place-items-center rounded-lg transition-colors group-hover/l:bg-tsm15-acc/15 group-aria-[current=page]/l:bg-tsm15-acc/18"><svg class="size-[18px] shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 6h9"/><path d="M19 6h1"/><circle cx="16" cy="6" r="2.5"/><path d="M4 12h2"/><path d="M12 12h8"/><circle cx="9" cy="12" r="2.5"/><path d="M4 18h11"/><circle cx="18" cy="18" r="2.5"/></svg></span><span class="ml-1.5 max-w-40 overflow-hidden whitespace-nowrap transition-[max-width,opacity,margin] duration-350 ease-[cubic-bezier(0.4,0,0.2,1)] group-has-checked:max-w-0 group-has-checked:opacity-0 motion-reduce:transition-none group-has-checked:ml-0">Settings</span></a></li>
    </ul>
    <div class="flex items-center overflow-hidden border-t border-tsm15-acc/12 px-3 py-4 group-has-checked:justify-center group-has-checked:px-0">
      <span aria-hidden="true" class="grid size-9 shrink-0 place-items-center rounded-full bg-linear-135 from-tsm15-acc to-violet-500 text-xs font-bold text-white">WP</span>
      <span class="ml-2.5 max-w-40 overflow-hidden whitespace-nowrap transition-[max-width,opacity,margin] duration-350 ease-[cubic-bezier(0.4,0,0.2,1)] group-has-checked:max-w-0 group-has-checked:opacity-0 motion-reduce:transition-none group-has-checked:ml-0"><span class="block text-xs font-bold">W. Park</span><span class="block text-[10px] text-tsm15-mut">Admin</span></span>
    </div>
  </nav>
  <div class="min-w-0 flex-1 p-6">
    <div class="mx-auto max-w-3xl">
      <h1 class="text-xl font-extrabold">Dashboard</h1>
      <p class="mt-1.5 mb-6 max-w-prose text-[13px] leading-relaxed text-tsm15-mut text-pretty">Press the arrow beside the logo to collapse the sidebar to icons and back. One hidden checkbox, one width transition.</p>
      <div class="grid grid-cols-2 gap-2.5 md:grid-cols-4">
          <div class="rounded-xl border border-tsm15-acc/12 bg-tsm15-surface p-4"><p class="text-[22px] font-extrabold tabular-nums text-tsm15-acc2">$82k</p><p class="mt-1 text-[11px] text-tsm15-mut">Revenue</p></div>
          <div class="rounded-xl border border-tsm15-acc/12 bg-tsm15-surface p-4"><p class="text-[22px] font-extrabold tabular-nums text-tsm15-acc2">1,247</p><p class="mt-1 text-[11px] text-tsm15-mut">Orders</p></div>
          <div class="rounded-xl border border-tsm15-acc/12 bg-tsm15-surface p-4"><p class="text-[22px] font-extrabold tabular-nums text-tsm15-acc2">98%</p><p class="mt-1 text-[11px] text-tsm15-mut">Uptime</p></div>
          <div class="rounded-xl border border-tsm15-acc/12 bg-tsm15-surface p-4"><p class="text-[22px] font-extrabold tabular-nums text-tsm15-acc2">4.9 / 5</p><p class="mt-1 text-[11px] text-tsm15-mut">Rating</p></div>
      </div>
    </div>
  </div>
</section>
```
## CSS
```css
@import "tailwindcss";

@theme {
  --color-tsm15-bg: #09090b;
  --color-tsm15-bg: oklch(0.141 0.004 285.8);
  --color-tsm15-surface: #141418;
  --color-tsm15-surface: oklch(0.193 0.008 285.6);
  --color-tsm15-nav: #0f0f12;
  --color-tsm15-nav: oklch(0.17 0.006 285.7);
  --color-tsm15-acc: #e11d48;
  --color-tsm15-acc: oklch(0.586 0.222 17.6);
  --color-tsm15-acc2: #fb7185;
  --color-tsm15-acc2: oklch(0.719 0.169 13.4);
  --color-tsm15-ink: #f1f5f9;
  --color-tsm15-ink: oklch(0.968 0.007 247.9);
  --color-tsm15-mut: #a1a1aa;
  --color-tsm15-mut: oklch(0.712 0.013 286.1);
  --font-tsm15-sans: 'Sora', system-ui, sans-serif;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's utilities, so the
   stage is sized before the browser build compiles. */

.tsm-15 {
  width: 100%;
  min-height: 100svh;
}
```

How this works

Animate width, clip the rest. The nav transitions width; each label animates max-width to 0 alongside opacity, which keeps text on one line instead of re-wrapping as the column narrows:

<nav class="w-60 overflow-hidden transition-[width]
  group-has-checked:w-16">
  <span class="max-w-40 whitespace-nowrap
    transition-[max-width,opacity]
    group-has-checked:max-w-0 group-has-checked:opacity-0">

A focus ring for a distant label. The collapse button is a <label> deep inside the nav, so peer-* can't reach it. group-has-[input:focus-visible]: on the root asks “is the toggle focused?” from anywhere.

The name follows the state. Two sr-only spans — one group-has-checked:hidden, one hidden group-has-checked:inline — mean assistive tech hears the action the button will perform.

Make it yours

  • Thinner rail: change group-has-checked:w-16 to w-12 and the icon cells to size-8.
  • Start collapsed: add checked to the input.
  • Remember the choice: one line of JS can persist input.checked to localStorage.
  • Keyboard shortcut: bind ⌘\\ to input.click(), VS Code-style.

Gotchas — read before shipping

  • transition-[width] triggers layout each frame — fine for a sidebar, but avoid heavy content inside.
  • whitespace-nowrap plus overflow-hidden is required on every collapsing label, or text spills during the animation.
  • Don't aria-hidden the collapsed labels — they are the links' only accessible names in rail mode.

Browser support

ChromeSafariFirefoxEdge
111+16.4+128+111+

:has() is Chrome 105 / Safari 15.4 / Firefox 121; the floor is v4's own sheet.

Techniques used in this demo

Utilitiesfirst-class utility or variant

Search CodeFronts

Loading…