30 Tailwind CSS Sidebar Layouts
02 / 30

All 30 in 30 Tailwind CSS Sidebar Layouts

  1. 01Tailwind SaaS Dashboard Sidebar with Native Dark Mode Switch
  2. 02Tailwind Collapsible Admin Sidebar with Icon Tooltips
  3. 03Tailwind Documentation Sidebar with Multi-Level :target Tree
  4. 04Tailwind Responsive Sidebar to Bottom Navigation Switch Layout
  5. 05Tailwind Analytics Sidebar with CSS Subgrid Column Alignment
  6. 06Tailwind Animated Collapsible Sidebar with Icon-Only Mode
  7. 07Tailwind Sticky Sidebar Layout with Scroll-Driven Progress Bar
  8. 08Tailwind Pure CSS Collapsible Sidebar Drawer without JavaScript
  9. 09Tailwind Fixed Sidebar Layout with Independently Scrolling Content
  10. 10Tailwind Off-Canvas Navigation Drawer with the Popover API
  11. 11Tailwind Right-Aligned Sidebar Layout for Inspector Panels
  12. 12Tailwind Two-Pane Webmail Layout with List and Reading Pane
  13. 13Tailwind Dual Sidebar App Shell with Left Nav and Context Panel
  14. 14Tailwind Mini Icon Rail Sidebar with Anchor-Positioned Active Pill
  15. 15Tailwind E-Commerce Filter Sidebar with Native Details Accordion
  16. 16Tailwind Floating Glassmorphism Sidebar with Gradient Hairline
  17. 17Tailwind Sidebar Menu with Pinned User Profile Footer
  18. 18Tailwind Push-Content Sidebar Drawer with GPU Transitions
  19. 19Tailwind Off-Canvas Cart Overlay with Blurred Backdrop Scrim
  20. 20Tailwind Sidebar with Persistent Collapsed State in localStorage
  21. 21Tailwind Sidebar with Sticky Search Bar Inside the Rail
  22. 22Tailwind Section-Grouped Sidebar Navigation with Header Labels
  23. 23Tailwind Sliding Active Indicator Sidebar with CSS :target
  24. 24Tailwind Multi-Level Nested Tree Sidebar with Details Branches
  25. 25Tailwind Tabbed Sidebar with Radio-Driven Panel Switching
  26. 26Tailwind Onboarding Wizard Sidebar with Step Progress Tracking
  27. 27Tailwind Resizable Sidebar Layout Using the CSS Resize Property
  28. 28Tailwind Three-Pane Workspace Shell with Icon Rail and List
  29. 29Tailwind Adaptive Sidebar Layout Using CSS Container Queries
  30. 30Tailwind Frosted Glass Floating Sidebar with Animated Aurora
Pure TailwindMIT licensed

Tailwind Collapsible Admin Sidebar with Icon Tooltips

An enterprise rail that folds from 240px of labelled navigation down to a 64px icon strip, where every icon grows a tooltip on hover and keyboard focus. The port's move is has-checked: — the collapsed state is declared on each element that changes, instead of in a distant block of descendant selectors.

Live Demo
Plain CSSTailwind v4
Try it

Mosaic by Cruip: a complete Tailwind admin dashboard template in HTML, React, Next.js and Vue · $69

Cruip · Mosaic by Cruip · Affiliate link

View Mosaic dashboard

The code

<section class="tsl-02 w-full h-svh min-h-svh grid grid-cols-[240px_1fr] transition-[grid-template-columns] duration-300 ease-out font-tsl02-sans bg-tsl02-bg text-tsl02-ink motion-reduce:transition-none">
  <input type="checkbox" id="tsl02-fold" class="sr-only peer">
  <aside class="flex flex-col gap-0.5 px-2.5 py-3.5 bg-tsl02-panel border-r border-tsl02-line overflow-visible">
    <label for="tsl02-fold" data-tooltip="Expand" class="group relative grid grid-cols-[auto_1fr] items-center gap-x-3 min-h-11 px-3 mb-2.5 rounded-[10px] text-sm font-medium text-tsl02-ink cursor-pointer transition-colors duration-200 hover:bg-tsl02-hover after:content-[attr(data-tooltip)] after:absolute after:left-[calc(100%+14px)] after:top-1/2 after:-translate-y-1/2 after:-translate-x-1 after:px-2.5 after:py-[7px] after:rounded-lg after:bg-tsl02-tip after:border after:border-tsl02-line after:text-tsl02-ink after:text-[12.5px] after:font-medium after:whitespace-nowrap after:shadow-[0_6px_18px_oklch(0_0_0/0.4)] after:opacity-0 after:pointer-events-none after:transition-[opacity,translate] after:duration-200 after:delay-150 after:z-10 motion-reduce:after:transition-opacity">
      <svg viewBox="0 0 24 24" aria-hidden="true" class="tsl-02-chev w-[18px] h-[18px] fill-none stroke-current stroke-[1.8] [stroke-linecap:round] [stroke-linejoin:round] transition-[rotate] duration-300 motion-reduce:transition-none"><path d="m14 6-6 6 6 6"/></svg>
      <span class="tsl-02-label grid grid-cols-[1fr] overflow-hidden whitespace-nowrap opacity-100 transition-[grid-template-columns,opacity] duration-300"><span class="min-w-0 overflow-hidden">Collapse</span></span>
    </label>
    <nav aria-label="Admin" class="flex flex-col gap-0.5">
      <a href="#" aria-current="page" data-tooltip="Dashboard" class="relative grid grid-cols-[auto_1fr] items-center gap-x-3 min-h-11 px-3 rounded-[10px] text-sm font-medium text-tsl02-mut no-underline transition-colors duration-200 hover:text-tsl02-ink hover:bg-tsl02-hover aria-[current=page]:text-tsl02-ink aria-[current=page]:bg-tsl02-active focus-visible:outline-2 focus-visible:outline-tsl02-accent focus-visible:outline-offset-2 after:content-[attr(data-tooltip)] after:absolute after:left-[calc(100%+14px)] after:top-1/2 after:-translate-y-1/2 after:-translate-x-1 after:px-2.5 after:py-[7px] after:rounded-lg after:bg-tsl02-tip after:border after:border-tsl02-line after:text-tsl02-ink after:text-[12.5px] after:font-medium after:whitespace-nowrap after:shadow-[0_6px_18px_oklch(0_0_0/0.4)] after:opacity-0 after:pointer-events-none after:transition-[opacity,translate] after:duration-200 after:delay-150 after:z-10 motion-reduce:after:transition-opacity"><svg viewBox="0 0 24 24" aria-hidden="true" class="w-[18px] h-[18px] fill-none stroke-current stroke-[1.8] [stroke-linecap:round] [stroke-linejoin:round]"><rect x="3" y="3" width="7.5" height="7.5" rx="1.5"/><rect x="13.5" y="3" width="7.5" height="7.5" rx="1.5"/><rect x="3" y="13.5" width="7.5" height="7.5" rx="1.5"/><rect x="13.5" y="13.5" width="7.5" height="7.5" rx="1.5"/></svg><span class="tsl-02-label grid grid-cols-[1fr] overflow-hidden whitespace-nowrap opacity-100 transition-[grid-template-columns,opacity] duration-300"><span class="min-w-0 overflow-hidden">Dashboard</span></span></a>
      <a href="#" data-tooltip="Users" class="relative grid grid-cols-[auto_1fr] items-center gap-x-3 min-h-11 px-3 rounded-[10px] text-sm font-medium text-tsl02-mut no-underline transition-colors duration-200 hover:text-tsl02-ink hover:bg-tsl02-hover focus-visible:outline-2 focus-visible:outline-tsl02-accent focus-visible:outline-offset-2 after:content-[attr(data-tooltip)] after:absolute after:left-[calc(100%+14px)] after:top-1/2 after:-translate-y-1/2 after:-translate-x-1 after:px-2.5 after:py-[7px] after:rounded-lg after:bg-tsl02-tip after:border after:border-tsl02-line after:text-tsl02-ink after:text-[12.5px] after:font-medium after:whitespace-nowrap after:shadow-[0_6px_18px_oklch(0_0_0/0.4)] after:opacity-0 after:pointer-events-none after:transition-[opacity,translate] after:duration-200 after:delay-150 after:z-10 motion-reduce:after:transition-opacity"><svg viewBox="0 0 24 24" aria-hidden="true" class="w-[18px] h-[18px] fill-none stroke-current stroke-[1.8] [stroke-linecap:round] [stroke-linejoin:round]"><circle cx="9" cy="8" r="3.5"/><path d="M3 20c0-3.3 2.7-5.5 6-5.5s6 2.2 6 5.5m1-13.5a3.5 3.5 0 0 1 0 7"/></svg><span class="tsl-02-label grid grid-cols-[1fr] overflow-hidden whitespace-nowrap opacity-100 transition-[grid-template-columns,opacity] duration-300"><span class="min-w-0 overflow-hidden">Users</span></span></a>
      <a href="#" data-tooltip="Reports" class="relative grid grid-cols-[auto_1fr] items-center gap-x-3 min-h-11 px-3 rounded-[10px] text-sm font-medium text-tsl02-mut no-underline transition-colors duration-200 hover:text-tsl02-ink hover:bg-tsl02-hover focus-visible:outline-2 focus-visible:outline-tsl02-accent focus-visible:outline-offset-2 after:content-[attr(data-tooltip)] after:absolute after:left-[calc(100%+14px)] after:top-1/2 after:-translate-y-1/2 after:-translate-x-1 after:px-2.5 after:py-[7px] after:rounded-lg after:bg-tsl02-tip after:border after:border-tsl02-line after:text-tsl02-ink after:text-[12.5px] after:font-medium after:whitespace-nowrap after:shadow-[0_6px_18px_oklch(0_0_0/0.4)] after:opacity-0 after:pointer-events-none after:transition-[opacity,translate] after:duration-200 after:delay-150 after:z-10 motion-reduce:after:transition-opacity"><svg viewBox="0 0 24 24" aria-hidden="true" class="w-[18px] h-[18px] fill-none stroke-current stroke-[1.8] [stroke-linecap:round] [stroke-linejoin:round]"><path d="M4 20V10m6 10V4m6 16v-7"/></svg><span class="tsl-02-label grid grid-cols-[1fr] overflow-hidden whitespace-nowrap opacity-100 transition-[grid-template-columns,opacity] duration-300"><span class="min-w-0 overflow-hidden">Reports</span></span></a>
      <a href="#" data-tooltip="Audit log" class="relative grid grid-cols-[auto_1fr] items-center gap-x-3 min-h-11 px-3 rounded-[10px] text-sm font-medium text-tsl02-mut no-underline transition-colors duration-200 hover:text-tsl02-ink hover:bg-tsl02-hover focus-visible:outline-2 focus-visible:outline-tsl02-accent focus-visible:outline-offset-2 after:content-[attr(data-tooltip)] after:absolute after:left-[calc(100%+14px)] after:top-1/2 after:-translate-y-1/2 after:-translate-x-1 after:px-2.5 after:py-[7px] after:rounded-lg after:bg-tsl02-tip after:border after:border-tsl02-line after:text-tsl02-ink after:text-[12.5px] after:font-medium after:whitespace-nowrap after:shadow-[0_6px_18px_oklch(0_0_0/0.4)] after:opacity-0 after:pointer-events-none after:transition-[opacity,translate] after:duration-200 after:delay-150 after:z-10 motion-reduce:after:transition-opacity"><svg viewBox="0 0 24 24" aria-hidden="true" class="w-[18px] h-[18px] fill-none stroke-current stroke-[1.8] [stroke-linecap:round] [stroke-linejoin:round]"><path d="M8 6h13M8 12h13M8 18h13M3.5 6h.01M3.5 12h.01M3.5 18h.01"/></svg><span class="tsl-02-label grid grid-cols-[1fr] overflow-hidden whitespace-nowrap opacity-100 transition-[grid-template-columns,opacity] duration-300"><span class="min-w-0 overflow-hidden">Audit log</span></span></a>
      <a href="#" data-tooltip="Settings" class="relative grid grid-cols-[auto_1fr] items-center gap-x-3 min-h-11 px-3 rounded-[10px] text-sm font-medium text-tsl02-mut no-underline transition-colors duration-200 hover:text-tsl02-ink hover:bg-tsl02-hover focus-visible:outline-2 focus-visible:outline-tsl02-accent focus-visible:outline-offset-2 after:content-[attr(data-tooltip)] after:absolute after:left-[calc(100%+14px)] after:top-1/2 after:-translate-y-1/2 after:-translate-x-1 after:px-2.5 after:py-[7px] after:rounded-lg after:bg-tsl02-tip after:border after:border-tsl02-line after:text-tsl02-ink after:text-[12.5px] after:font-medium after:whitespace-nowrap after:shadow-[0_6px_18px_oklch(0_0_0/0.4)] after:opacity-0 after:pointer-events-none after:transition-[opacity,translate] after:duration-200 after:delay-150 after:z-10 motion-reduce:after:transition-opacity"><svg viewBox="0 0 24 24" aria-hidden="true" class="w-[18px] h-[18px] fill-none stroke-current stroke-[1.8] [stroke-linecap:round] [stroke-linejoin:round]"><circle cx="12" cy="12" r="3"/><path d="M12 2v3m0 14v3M2 12h3m14 0h3M4.9 4.9l2.1 2.1m10 10 2.1 2.1m0-14.2-2.1 2.1m-10 10-2.1 2.1"/></svg><span class="tsl-02-label grid grid-cols-[1fr] overflow-hidden whitespace-nowrap opacity-100 transition-[grid-template-columns,opacity] duration-300"><span class="min-w-0 overflow-hidden">Settings</span></span></a>
    </nav>
  </aside>
  <main class="p-8 overflow-y-auto [scrollbar-gutter:stable]">
    <h1 class="text-[22px] font-semibold leading-tight mb-2.5">User management</h1>
    <p class="max-w-[44ch] text-sm leading-relaxed text-tsl02-mut text-pretty">Toggle the chevron &mdash; labels fold into a 0fr grid track and tooltips take over.</p>
  </main>
</section>
@import "tailwindcss";

@theme {
  --color-tsl02-bg: #121319;
  --color-tsl02-bg: oklch(0.13 0.015 275);
  --color-tsl02-panel: #181a21;
  --color-tsl02-panel: oklch(0.16 0.02 275);
  --color-tsl02-tip: #24262f;
  --color-tsl02-tip: oklch(0.24 0.02 275);
  --color-tsl02-ink: #e8e9ee;
  --color-tsl02-ink: oklch(0.92 0.005 275);
  --color-tsl02-mut: #94969f;
  --color-tsl02-mut: oklch(0.64 0.012 275);
  --color-tsl02-line: #383a44;
  --color-tsl02-line: oklch(0.27 0.02 275);
  --color-tsl02-accent: #8a7cf0;
  --color-tsl02-accent: oklch(0.68 0.17 275);
  --color-tsl02-hover: rgb(232 233 238 / 0.07);
  --color-tsl02-hover: color-mix(in oklab, oklch(0.92 0.005 275) 7%, transparent);
  --color-tsl02-active: rgb(138 124 240 / 0.2);
  --color-tsl02-active: color-mix(in oklab, oklch(0.68 0.17 275) 20%, transparent);
  --font-tsl02-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}

.tsl-02 {
  width: 100%;
  height: 100svh;
  min-height: 100svh;
  color-scheme: dark;
}
/* STATE — one checkbox at the shell root, read by the shell, the label cells,
   the chevron and the tooltips. */

.tsl-02:has(#tsl02-fold:checked) {
  grid-template-columns: 64px 1fr;
}

#tsl02-fold:checked ~ aside .tsl-02-label {
  grid-template-columns: 0fr;
  opacity: 0;
}

#tsl02-fold:checked ~ aside .tsl-02-chev {
  rotate: 180deg;
}

#tsl02-fold:checked ~ aside [data-tooltip]:hover::after,
#tsl02-fold:checked ~ aside [data-tooltip]:focus-visible::after {
  opacity: 1;
  translate: 0;
}

#tsl02-fold:focus-visible ~ aside label[for="tsl02-fold"] {
  outline: 2px solid var(--color-tsl02-accent);
  outline-offset: 2px;
}
/* The tooltip pill must escape the rail; the rail therefore never clips. */

.tsl-02 aside {
  overflow: visible;
}

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-tsl02-bg: #121319;
  --color-tsl02-bg: oklch(0.13 0.015 275);
  --color-tsl02-panel: #181a21;
  --color-tsl02-panel: oklch(0.16 0.02 275);
  --color-tsl02-tip: #24262f;
  --color-tsl02-tip: oklch(0.24 0.02 275);
  --color-tsl02-ink: #e8e9ee;
  --color-tsl02-ink: oklch(0.92 0.005 275);
  --color-tsl02-mut: #94969f;
  --color-tsl02-mut: oklch(0.64 0.012 275);
  --color-tsl02-line: #383a44;
  --color-tsl02-line: oklch(0.27 0.02 275);
  --color-tsl02-accent: #8a7cf0;
  --color-tsl02-accent: oklch(0.68 0.17 275);
  --color-tsl02-hover: rgb(232 233 238 / 0.07);
  --color-tsl02-hover: color-mix(in oklab, oklch(0.92 0.005 275) 7%, transparent);
  --color-tsl02-active: rgb(138 124 240 / 0.2);
  --color-tsl02-active: color-mix(in oklab, oklch(0.68 0.17 275) 20%, transparent);
  --font-tsl02-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}

.tsl-02 {
  width: 100%;
  height: 100svh;
  min-height: 100svh;
  color-scheme: dark;
}
/* STATE — one checkbox at the shell root, read by the shell, the label cells,
   the chevron and the tooltips. */

.tsl-02:has(#tsl02-fold:checked) {
  grid-template-columns: 64px 1fr;
}

#tsl02-fold:checked ~ aside .tsl-02-label {
  grid-template-columns: 0fr;
  opacity: 0;
}

#tsl02-fold:checked ~ aside .tsl-02-chev {
  rotate: 180deg;
}

#tsl02-fold:checked ~ aside [data-tooltip]:hover::after,
#tsl02-fold:checked ~ aside [data-tooltip]:focus-visible::after {
  opacity: 1;
  translate: 0;
}

#tsl02-fold:focus-visible ~ aside label[for="tsl02-fold"] {
  outline: 2px solid var(--color-tsl02-accent);
  outline-offset: 2px;
}
/* The tooltip pill must escape the rail; the rail therefore never clips. */

.tsl-02 aside {
  overflow: visible;
}
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=Plus+Jakarta+Sans: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.

  • Three descendant selectors collapse into one variant used three times. has-checked: on the shell, on each label cell and on the chevron reads the same input from three different depths — no .sbl-02:has(#fold:checked) .sbl-02__label chains, and each element declares its own collapsed state.
  • The tooltip is the one place the utility form is genuinely longer than the CSS. It is still the right trade: the gating condition (has-checked:hover:after:) is visible on the link that owns the tooltip, rather than in a rule three screens away.
  • UI change from the CSS original: the tooltip now fires on focus-visible for the fold control as well as the links, rows are min-h-11 throughout, and the label cell keeps a nested min-w-0 overflow-hidden wrapper — without it Safari lets the text overflow the 0fr track during the tween.
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 Admin Sidebar with Icon Tooltips
Source: https://codefronts.com/layouts/tailwind-css-sidebar-layouts/tailwind-collapsible-admin-sidebar-icon-tooltips/

An enterprise rail that folds from 240px of labelled navigation down to a 64px icon strip, where every icon grows a tooltip on hover and keyboard focus. The port's move is has-checked: &mdash; the collapsed state is declared on each element that changes, instead of in a distant block of descendant selectors.
## HTML
```html
<section class="tsl-02 w-full h-svh min-h-svh grid grid-cols-[240px_1fr] transition-[grid-template-columns] duration-300 ease-out font-tsl02-sans bg-tsl02-bg text-tsl02-ink motion-reduce:transition-none">
  <input type="checkbox" id="tsl02-fold" class="sr-only peer">
  <aside class="flex flex-col gap-0.5 px-2.5 py-3.5 bg-tsl02-panel border-r border-tsl02-line overflow-visible">
    <label for="tsl02-fold" data-tooltip="Expand" class="group relative grid grid-cols-[auto_1fr] items-center gap-x-3 min-h-11 px-3 mb-2.5 rounded-[10px] text-sm font-medium text-tsl02-ink cursor-pointer transition-colors duration-200 hover:bg-tsl02-hover after:content-[attr(data-tooltip)] after:absolute after:left-[calc(100%+14px)] after:top-1/2 after:-translate-y-1/2 after:-translate-x-1 after:px-2.5 after:py-[7px] after:rounded-lg after:bg-tsl02-tip after:border after:border-tsl02-line after:text-tsl02-ink after:text-[12.5px] after:font-medium after:whitespace-nowrap after:shadow-[0_6px_18px_oklch(0_0_0/0.4)] after:opacity-0 after:pointer-events-none after:transition-[opacity,translate] after:duration-200 after:delay-150 after:z-10 motion-reduce:after:transition-opacity">
      <svg viewBox="0 0 24 24" aria-hidden="true" class="tsl-02-chev w-[18px] h-[18px] fill-none stroke-current stroke-[1.8] [stroke-linecap:round] [stroke-linejoin:round] transition-[rotate] duration-300 motion-reduce:transition-none"><path d="m14 6-6 6 6 6"/></svg>
      <span class="tsl-02-label grid grid-cols-[1fr] overflow-hidden whitespace-nowrap opacity-100 transition-[grid-template-columns,opacity] duration-300"><span class="min-w-0 overflow-hidden">Collapse</span></span>
    </label>
    <nav aria-label="Admin" class="flex flex-col gap-0.5">
      <a href="#" aria-current="page" data-tooltip="Dashboard" class="relative grid grid-cols-[auto_1fr] items-center gap-x-3 min-h-11 px-3 rounded-[10px] text-sm font-medium text-tsl02-mut no-underline transition-colors duration-200 hover:text-tsl02-ink hover:bg-tsl02-hover aria-[current=page]:text-tsl02-ink aria-[current=page]:bg-tsl02-active focus-visible:outline-2 focus-visible:outline-tsl02-accent focus-visible:outline-offset-2 after:content-[attr(data-tooltip)] after:absolute after:left-[calc(100%+14px)] after:top-1/2 after:-translate-y-1/2 after:-translate-x-1 after:px-2.5 after:py-[7px] after:rounded-lg after:bg-tsl02-tip after:border after:border-tsl02-line after:text-tsl02-ink after:text-[12.5px] after:font-medium after:whitespace-nowrap after:shadow-[0_6px_18px_oklch(0_0_0/0.4)] after:opacity-0 after:pointer-events-none after:transition-[opacity,translate] after:duration-200 after:delay-150 after:z-10 motion-reduce:after:transition-opacity"><svg viewBox="0 0 24 24" aria-hidden="true" class="w-[18px] h-[18px] fill-none stroke-current stroke-[1.8] [stroke-linecap:round] [stroke-linejoin:round]"><rect x="3" y="3" width="7.5" height="7.5" rx="1.5"/><rect x="13.5" y="3" width="7.5" height="7.5" rx="1.5"/><rect x="3" y="13.5" width="7.5" height="7.5" rx="1.5"/><rect x="13.5" y="13.5" width="7.5" height="7.5" rx="1.5"/></svg><span class="tsl-02-label grid grid-cols-[1fr] overflow-hidden whitespace-nowrap opacity-100 transition-[grid-template-columns,opacity] duration-300"><span class="min-w-0 overflow-hidden">Dashboard</span></span></a>
      <a href="#" data-tooltip="Users" class="relative grid grid-cols-[auto_1fr] items-center gap-x-3 min-h-11 px-3 rounded-[10px] text-sm font-medium text-tsl02-mut no-underline transition-colors duration-200 hover:text-tsl02-ink hover:bg-tsl02-hover focus-visible:outline-2 focus-visible:outline-tsl02-accent focus-visible:outline-offset-2 after:content-[attr(data-tooltip)] after:absolute after:left-[calc(100%+14px)] after:top-1/2 after:-translate-y-1/2 after:-translate-x-1 after:px-2.5 after:py-[7px] after:rounded-lg after:bg-tsl02-tip after:border after:border-tsl02-line after:text-tsl02-ink after:text-[12.5px] after:font-medium after:whitespace-nowrap after:shadow-[0_6px_18px_oklch(0_0_0/0.4)] after:opacity-0 after:pointer-events-none after:transition-[opacity,translate] after:duration-200 after:delay-150 after:z-10 motion-reduce:after:transition-opacity"><svg viewBox="0 0 24 24" aria-hidden="true" class="w-[18px] h-[18px] fill-none stroke-current stroke-[1.8] [stroke-linecap:round] [stroke-linejoin:round]"><circle cx="9" cy="8" r="3.5"/><path d="M3 20c0-3.3 2.7-5.5 6-5.5s6 2.2 6 5.5m1-13.5a3.5 3.5 0 0 1 0 7"/></svg><span class="tsl-02-label grid grid-cols-[1fr] overflow-hidden whitespace-nowrap opacity-100 transition-[grid-template-columns,opacity] duration-300"><span class="min-w-0 overflow-hidden">Users</span></span></a>
      <a href="#" data-tooltip="Reports" class="relative grid grid-cols-[auto_1fr] items-center gap-x-3 min-h-11 px-3 rounded-[10px] text-sm font-medium text-tsl02-mut no-underline transition-colors duration-200 hover:text-tsl02-ink hover:bg-tsl02-hover focus-visible:outline-2 focus-visible:outline-tsl02-accent focus-visible:outline-offset-2 after:content-[attr(data-tooltip)] after:absolute after:left-[calc(100%+14px)] after:top-1/2 after:-translate-y-1/2 after:-translate-x-1 after:px-2.5 after:py-[7px] after:rounded-lg after:bg-tsl02-tip after:border after:border-tsl02-line after:text-tsl02-ink after:text-[12.5px] after:font-medium after:whitespace-nowrap after:shadow-[0_6px_18px_oklch(0_0_0/0.4)] after:opacity-0 after:pointer-events-none after:transition-[opacity,translate] after:duration-200 after:delay-150 after:z-10 motion-reduce:after:transition-opacity"><svg viewBox="0 0 24 24" aria-hidden="true" class="w-[18px] h-[18px] fill-none stroke-current stroke-[1.8] [stroke-linecap:round] [stroke-linejoin:round]"><path d="M4 20V10m6 10V4m6 16v-7"/></svg><span class="tsl-02-label grid grid-cols-[1fr] overflow-hidden whitespace-nowrap opacity-100 transition-[grid-template-columns,opacity] duration-300"><span class="min-w-0 overflow-hidden">Reports</span></span></a>
      <a href="#" data-tooltip="Audit log" class="relative grid grid-cols-[auto_1fr] items-center gap-x-3 min-h-11 px-3 rounded-[10px] text-sm font-medium text-tsl02-mut no-underline transition-colors duration-200 hover:text-tsl02-ink hover:bg-tsl02-hover focus-visible:outline-2 focus-visible:outline-tsl02-accent focus-visible:outline-offset-2 after:content-[attr(data-tooltip)] after:absolute after:left-[calc(100%+14px)] after:top-1/2 after:-translate-y-1/2 after:-translate-x-1 after:px-2.5 after:py-[7px] after:rounded-lg after:bg-tsl02-tip after:border after:border-tsl02-line after:text-tsl02-ink after:text-[12.5px] after:font-medium after:whitespace-nowrap after:shadow-[0_6px_18px_oklch(0_0_0/0.4)] after:opacity-0 after:pointer-events-none after:transition-[opacity,translate] after:duration-200 after:delay-150 after:z-10 motion-reduce:after:transition-opacity"><svg viewBox="0 0 24 24" aria-hidden="true" class="w-[18px] h-[18px] fill-none stroke-current stroke-[1.8] [stroke-linecap:round] [stroke-linejoin:round]"><path d="M8 6h13M8 12h13M8 18h13M3.5 6h.01M3.5 12h.01M3.5 18h.01"/></svg><span class="tsl-02-label grid grid-cols-[1fr] overflow-hidden whitespace-nowrap opacity-100 transition-[grid-template-columns,opacity] duration-300"><span class="min-w-0 overflow-hidden">Audit log</span></span></a>
      <a href="#" data-tooltip="Settings" class="relative grid grid-cols-[auto_1fr] items-center gap-x-3 min-h-11 px-3 rounded-[10px] text-sm font-medium text-tsl02-mut no-underline transition-colors duration-200 hover:text-tsl02-ink hover:bg-tsl02-hover focus-visible:outline-2 focus-visible:outline-tsl02-accent focus-visible:outline-offset-2 after:content-[attr(data-tooltip)] after:absolute after:left-[calc(100%+14px)] after:top-1/2 after:-translate-y-1/2 after:-translate-x-1 after:px-2.5 after:py-[7px] after:rounded-lg after:bg-tsl02-tip after:border after:border-tsl02-line after:text-tsl02-ink after:text-[12.5px] after:font-medium after:whitespace-nowrap after:shadow-[0_6px_18px_oklch(0_0_0/0.4)] after:opacity-0 after:pointer-events-none after:transition-[opacity,translate] after:duration-200 after:delay-150 after:z-10 motion-reduce:after:transition-opacity"><svg viewBox="0 0 24 24" aria-hidden="true" class="w-[18px] h-[18px] fill-none stroke-current stroke-[1.8] [stroke-linecap:round] [stroke-linejoin:round]"><circle cx="12" cy="12" r="3"/><path d="M12 2v3m0 14v3M2 12h3m14 0h3M4.9 4.9l2.1 2.1m10 10 2.1 2.1m0-14.2-2.1 2.1m-10 10-2.1 2.1"/></svg><span class="tsl-02-label grid grid-cols-[1fr] overflow-hidden whitespace-nowrap opacity-100 transition-[grid-template-columns,opacity] duration-300"><span class="min-w-0 overflow-hidden">Settings</span></span></a>
    </nav>
  </aside>
  <main class="p-8 overflow-y-auto [scrollbar-gutter:stable]">
    <h1 class="text-[22px] font-semibold leading-tight mb-2.5">User management</h1>
    <p class="max-w-[44ch] text-sm leading-relaxed text-tsl02-mut text-pretty">Toggle the chevron &mdash; labels fold into a 0fr grid track and tooltips take over.</p>
  </main>
</section>
```
## CSS
```css
@import "tailwindcss";

@theme {
  --color-tsl02-bg: #121319;
  --color-tsl02-bg: oklch(0.13 0.015 275);
  --color-tsl02-panel: #181a21;
  --color-tsl02-panel: oklch(0.16 0.02 275);
  --color-tsl02-tip: #24262f;
  --color-tsl02-tip: oklch(0.24 0.02 275);
  --color-tsl02-ink: #e8e9ee;
  --color-tsl02-ink: oklch(0.92 0.005 275);
  --color-tsl02-mut: #94969f;
  --color-tsl02-mut: oklch(0.64 0.012 275);
  --color-tsl02-line: #383a44;
  --color-tsl02-line: oklch(0.27 0.02 275);
  --color-tsl02-accent: #8a7cf0;
  --color-tsl02-accent: oklch(0.68 0.17 275);
  --color-tsl02-hover: rgb(232 233 238 / 0.07);
  --color-tsl02-hover: color-mix(in oklab, oklch(0.92 0.005 275) 7%, transparent);
  --color-tsl02-active: rgb(138 124 240 / 0.2);
  --color-tsl02-active: color-mix(in oklab, oklch(0.68 0.17 275) 20%, transparent);
  --font-tsl02-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}

.tsl-02 {
  width: 100%;
  height: 100svh;
  min-height: 100svh;
  color-scheme: dark;
}
/* STATE — one checkbox at the shell root, read by the shell, the label cells,
   the chevron and the tooltips. */

.tsl-02:has(#tsl02-fold:checked) {
  grid-template-columns: 64px 1fr;
}

#tsl02-fold:checked ~ aside .tsl-02-label {
  grid-template-columns: 0fr;
  opacity: 0;
}

#tsl02-fold:checked ~ aside .tsl-02-chev {
  rotate: 180deg;
}

#tsl02-fold:checked ~ aside [data-tooltip]:hover::after,
#tsl02-fold:checked ~ aside [data-tooltip]:focus-visible::after {
  opacity: 1;
  translate: 0;
}

#tsl02-fold:focus-visible ~ aside label[for="tsl02-fold"] {
  outline: 2px solid var(--color-tsl02-accent);
  outline-offset: 2px;
}
/* The tooltip pill must escape the rail; the rail therefore never clips. */

.tsl-02 aside {
  overflow: visible;
}
```

How this works

One checkbox, read from three different heights. The CSS version wrote .sbl-02:has(#fold:checked) .sbl-02__label three times over. Tailwind states each consequence on the element that has it:

<section class="grid grid-cols-[240px_1fr] has-checked:grid-cols-[64px_1fr]">
  <span class="grid grid-cols-[1fr] has-checked:grid-cols-[0fr] overflow-hidden">

Both read the same input; neither needs to know where it lives. Grid tracks are animatable in every modern engine, so transition-[grid-template-columns] moves the sidebar edge and reflows the content pane in one composited motion.

The tooltip stays CSS-adjacent but becomes a class list. after:content-[attr(data-tooltip)] pulls the text from the attribute, and the gating condition — only when collapsed, only on hover or focus — is three stacked variants: has-checked:hover:after:opacity-100 has-checked:focus-visible:after:opacity-100. Because the tooltip is a pseudo-element of the real link, the clipped label is still the accessible name; the tooltip is visual redundancy only.

Make it yours

  • Collapsed width: has-checked:grid-cols-[64px_1fr] fits 20px icons with 44px targets; 72px if you add a badge column.
  • Tooltip delay: after:transition-[opacity,translate] after:delay-150 stops tooltips strobing as the cursor travels the rail.
  • Auto-collapse on small screens: add max-lg:grid-cols-[64px_1fr] to the shell and the checkbox becomes a desktop-only preference.

Gotchas — read before shipping

  • Pseudo-element tooltips cannot escape an overflow-hidden ancestor — keep overflow-hidden on the label cell only, never on the link or the <aside>.
  • Grid-track animation needs two explicit endpoints; auto as one of them will not tween.
  • Use sr-only-adjacent clipping (overflow-hidden + 0fr), not hidden, for the label — display:none removes the link's accessible name and fails WCAG.
  • has-checked: matches ANY checked descendant. With more than one checkbox in the shell, scope it: has-[#tsl02-fold:checked]:.

Browser support

ChromeSafariFirefoxEdge
111+16.4+128+111+

Animatable grid tracks (Chrome 107, Safari 16) and :has() set the real floor; without them the layout still collapses, just without the tween.

Techniques used in this demo

Utilitiesfirst-class utility or variant

HTML & JSno Tailwind involved

Search CodeFronts

Loading…