30 Tailwind CSS Sidebar Layouts
08 / 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 Pure CSS Collapsible Sidebar Drawer without JavaScript

The zero-JS drawer done the 2026 way: a hidden checkbox holds state, has-checked: reads it from any depth, and the panel slides on a GPU-composited transform. The hamburger morphs into an X from the same signal, using nth-child arbitrary variants.

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-08 relative w-full h-svh min-h-svh grid grid-rows-[auto_1fr] overflow-hidden font-tsl08-sans bg-tsl08-bg text-tsl08-ink">
  <input type="checkbox" id="tsl08-t" class="sr-only peer">
  <header class="flex items-center gap-3.5 h-[54px] px-4 bg-tsl08-panel border-b border-tsl08-line text-[15px] font-semibold">
    <label for="tsl08-t" aria-hidden="true" class="grid gap-[4.5px] -ml-2.5 px-2.5 py-3 rounded-[9px] cursor-pointer">
      <span class="tsl-08-b1 block w-[19px] h-0.5 rounded-sm bg-tsl08-ink transition-[rotate,translate,opacity] duration-300 motion-reduce:transition-none"></span>
      <span class="tsl-08-b2 block w-[19px] h-0.5 rounded-sm bg-tsl08-ink transition-opacity duration-200"></span>
      <span class="tsl-08-b3 block w-[19px] h-0.5 rounded-sm bg-tsl08-ink transition-[rotate,translate,opacity] duration-300 motion-reduce:transition-none"></span>
    </label>
    <strong>Fieldnotes</strong>
  </header>
  <label for="tsl08-t" aria-hidden="true" class="tsl-08-scrim absolute inset-x-0 bottom-0 top-[54px] z-10 bg-[oklch(0_0_0/0.5)] backdrop-blur-[2px] opacity-0 pointer-events-none transition-opacity duration-300 motion-reduce:transition-none"></label>
  <aside aria-label="Menu" class="tsl-08-drawer absolute left-0 top-[54px] bottom-0 z-20 w-[min(280px,85vw)] px-3 py-4 bg-tsl08-panel border-r border-tsl08-line -translate-x-full invisible transition-[translate,visibility] duration-[320ms] ease-[cubic-bezier(.4,0,.2,1)] delay-0 starting:-translate-x-full motion-reduce:transition-none [transition-behavior:allow-discrete]">
    <nav class="flex flex-col gap-0.5">
      <a href="#" aria-current="page" class="flex items-center min-h-11 px-3.5 rounded-[10px] text-sm font-medium text-tsl08-mut no-underline transition-colors duration-200 hover:text-tsl08-ink hover:bg-tsl08-hover aria-[current=page]:text-tsl08-ink aria-[current=page]:bg-tsl08-tint focus-visible:outline-2 focus-visible:outline-tsl08-accent focus-visible:outline-offset-2">All notes</a>
      <a href="#" class="flex items-center min-h-11 px-3.5 rounded-[10px] text-sm font-medium text-tsl08-mut no-underline transition-colors duration-200 hover:text-tsl08-ink hover:bg-tsl08-hover focus-visible:outline-2 focus-visible:outline-tsl08-accent focus-visible:outline-offset-2">Notebooks</a>
      <a href="#" class="flex items-center min-h-11 px-3.5 rounded-[10px] text-sm font-medium text-tsl08-mut no-underline transition-colors duration-200 hover:text-tsl08-ink hover:bg-tsl08-hover focus-visible:outline-2 focus-visible:outline-tsl08-accent focus-visible:outline-offset-2">Tags</a>
      <a href="#" class="flex items-center min-h-11 px-3.5 rounded-[10px] text-sm font-medium text-tsl08-mut no-underline transition-colors duration-200 hover:text-tsl08-ink hover:bg-tsl08-hover focus-visible:outline-2 focus-visible:outline-tsl08-accent focus-visible:outline-offset-2">Archive</a>
      <a href="#" class="flex items-center min-h-11 px-3.5 rounded-[10px] text-sm font-medium text-tsl08-mut no-underline transition-colors duration-200 hover:text-tsl08-ink hover:bg-tsl08-hover focus-visible:outline-2 focus-visible:outline-tsl08-accent focus-visible:outline-offset-2">Trash</a>
    </nav>
  </aside>
  <main class="p-7 overflow-y-auto [scrollbar-gutter:stable]">
    <h1 class="text-[22px] font-semibold leading-tight mb-2.5">All notes</h1>
    <p class="max-w-[46ch] text-sm leading-relaxed text-tsl08-mut text-pretty">Open the drawer &mdash; the only animated property is transform, so the slide is fully GPU-composited. Zero JavaScript on this page.</p>
  </main>
</section>
@import "tailwindcss";

@theme {
  --color-tsl08-bg: #16121b;
  --color-tsl08-bg: oklch(0.14 0.015 300);
  --color-tsl08-panel: #1d1824;
  --color-tsl08-panel: oklch(0.18 0.02 300);
  --color-tsl08-ink: #eae7ef;
  --color-tsl08-ink: oklch(0.93 0.005 300);
  --color-tsl08-mut: #9b95a4;
  --color-tsl08-mut: oklch(0.64 0.012 300);
  --color-tsl08-line: #3c3545;
  --color-tsl08-line: oklch(0.29 0.02 300);
  --color-tsl08-accent: #d07ae0;
  --color-tsl08-accent: oklch(0.72 0.16 320);
  --color-tsl08-hover: rgb(234 231 239 / 0.07);
  --color-tsl08-hover: color-mix(in oklab, oklch(0.93 0.005 300) 7%, transparent);
  --color-tsl08-tint: rgb(208 122 224 / 0.18);
  --color-tsl08-tint: color-mix(in oklab, oklch(0.72 0.16 320) 18%, transparent);
  --font-tsl08-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}

.tsl-08 {
  width: 100%;
  height: 100svh;
  min-height: 100svh;
  color-scheme: dark;
}
/* STATE — one checkbox, four consequences: drawer, scrim and the three bars. */

#tsl08-t:checked ~ .tsl-08-drawer {
  translate: 0;
  visibility: visible;
}

#tsl08-t:checked ~ .tsl-08-scrim {
  opacity: 1;
  pointer-events: auto;
}

#tsl08-t:checked ~ header .tsl-08-b1 {
  rotate: 45deg;
  translate: 0 6.5px;
}

#tsl08-t:checked ~ header .tsl-08-b2 {
  opacity: 0;
}

#tsl08-t:checked ~ header .tsl-08-b3 {
  rotate: -45deg;
  translate: 0 -6.5px;
}

#tsl08-t:focus-visible ~ header label[for="tsl08-t"] {
  outline: 2px solid var(--color-tsl08-accent);
  outline-offset: 2px;
}
/* visibility must lag the slide on close and lead it on open — one property
   with two different timings, which no single utility can express. */

.tsl-08 aside {
  transition-delay: 0s, 320ms;
}

.tsl-08:has(#tsl08-t:checked) aside {
  transition-delay: 0s, 0s;
}

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-tsl08-bg: #16121b;
  --color-tsl08-bg: oklch(0.14 0.015 300);
  --color-tsl08-panel: #1d1824;
  --color-tsl08-panel: oklch(0.18 0.02 300);
  --color-tsl08-ink: #eae7ef;
  --color-tsl08-ink: oklch(0.93 0.005 300);
  --color-tsl08-mut: #9b95a4;
  --color-tsl08-mut: oklch(0.64 0.012 300);
  --color-tsl08-line: #3c3545;
  --color-tsl08-line: oklch(0.29 0.02 300);
  --color-tsl08-accent: #d07ae0;
  --color-tsl08-accent: oklch(0.72 0.16 320);
  --color-tsl08-hover: rgb(234 231 239 / 0.07);
  --color-tsl08-hover: color-mix(in oklab, oklch(0.93 0.005 300) 7%, transparent);
  --color-tsl08-tint: rgb(208 122 224 / 0.18);
  --color-tsl08-tint: color-mix(in oklab, oklch(0.72 0.16 320) 18%, transparent);
  --font-tsl08-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}

.tsl-08 {
  width: 100%;
  height: 100svh;
  min-height: 100svh;
  color-scheme: dark;
}
/* STATE — one checkbox, four consequences: drawer, scrim and the three bars. */

#tsl08-t:checked ~ .tsl-08-drawer {
  translate: 0;
  visibility: visible;
}

#tsl08-t:checked ~ .tsl-08-scrim {
  opacity: 1;
  pointer-events: auto;
}

#tsl08-t:checked ~ header .tsl-08-b1 {
  rotate: 45deg;
  translate: 0 6.5px;
}

#tsl08-t:checked ~ header .tsl-08-b2 {
  opacity: 0;
}

#tsl08-t:checked ~ header .tsl-08-b3 {
  rotate: -45deg;
  translate: 0 -6.5px;
}

#tsl08-t:focus-visible ~ header label[for="tsl08-t"] {
  outline: 2px solid var(--color-tsl08-accent);
  outline-offset: 2px;
}
/* visibility must lag the slide on close and lead it on open — one property
   with two different timings, which no single utility can express. */

.tsl-08 aside {
  transition-delay: 0s, 320ms;
}

.tsl-08:has(#tsl08-t:checked) aside {
  transition-delay: 0s, 0s;
}
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.

  • This is the demo where the port stops being a translation and starts being a judgement call. The resting design is 100% utilities; the state machine is five scoped lines of CSS, because a checkbox at the shell root is an ANCESTOR of nothing and a sibling of everything.
  • The asymmetric visibility delay is the one thing that stayed CSS. Tailwind can express delay-[320ms], but not two different delays for two properties in one declaration — a two-line rule is honest here.
  • UI change from the CSS original: drawer links are now min-h-11 rather than 11px of padding, and the drawer carries transition-behavior:allow-discrete so the visibility swap interpolates properly in Chrome rather than snapping a frame early.
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 Pure CSS Collapsible Sidebar Drawer without JavaScript
Source: https://codefronts.com/layouts/tailwind-css-sidebar-layouts/tailwind-pure-css-collapsible-sidebar-drawer-no-javascript/

The zero-JS drawer done the 2026 way: a hidden checkbox holds state, has-checked: reads it from any depth, and the panel slides on a GPU-composited transform. The hamburger morphs into an X from the same signal, using nth-child arbitrary variants.
## HTML
```html
<section class="tsl-08 relative w-full h-svh min-h-svh grid grid-rows-[auto_1fr] overflow-hidden font-tsl08-sans bg-tsl08-bg text-tsl08-ink">
  <input type="checkbox" id="tsl08-t" class="sr-only peer">
  <header class="flex items-center gap-3.5 h-[54px] px-4 bg-tsl08-panel border-b border-tsl08-line text-[15px] font-semibold">
    <label for="tsl08-t" aria-hidden="true" class="grid gap-[4.5px] -ml-2.5 px-2.5 py-3 rounded-[9px] cursor-pointer">
      <span class="tsl-08-b1 block w-[19px] h-0.5 rounded-sm bg-tsl08-ink transition-[rotate,translate,opacity] duration-300 motion-reduce:transition-none"></span>
      <span class="tsl-08-b2 block w-[19px] h-0.5 rounded-sm bg-tsl08-ink transition-opacity duration-200"></span>
      <span class="tsl-08-b3 block w-[19px] h-0.5 rounded-sm bg-tsl08-ink transition-[rotate,translate,opacity] duration-300 motion-reduce:transition-none"></span>
    </label>
    <strong>Fieldnotes</strong>
  </header>
  <label for="tsl08-t" aria-hidden="true" class="tsl-08-scrim absolute inset-x-0 bottom-0 top-[54px] z-10 bg-[oklch(0_0_0/0.5)] backdrop-blur-[2px] opacity-0 pointer-events-none transition-opacity duration-300 motion-reduce:transition-none"></label>
  <aside aria-label="Menu" class="tsl-08-drawer absolute left-0 top-[54px] bottom-0 z-20 w-[min(280px,85vw)] px-3 py-4 bg-tsl08-panel border-r border-tsl08-line -translate-x-full invisible transition-[translate,visibility] duration-[320ms] ease-[cubic-bezier(.4,0,.2,1)] delay-0 starting:-translate-x-full motion-reduce:transition-none [transition-behavior:allow-discrete]">
    <nav class="flex flex-col gap-0.5">
      <a href="#" aria-current="page" class="flex items-center min-h-11 px-3.5 rounded-[10px] text-sm font-medium text-tsl08-mut no-underline transition-colors duration-200 hover:text-tsl08-ink hover:bg-tsl08-hover aria-[current=page]:text-tsl08-ink aria-[current=page]:bg-tsl08-tint focus-visible:outline-2 focus-visible:outline-tsl08-accent focus-visible:outline-offset-2">All notes</a>
      <a href="#" class="flex items-center min-h-11 px-3.5 rounded-[10px] text-sm font-medium text-tsl08-mut no-underline transition-colors duration-200 hover:text-tsl08-ink hover:bg-tsl08-hover focus-visible:outline-2 focus-visible:outline-tsl08-accent focus-visible:outline-offset-2">Notebooks</a>
      <a href="#" class="flex items-center min-h-11 px-3.5 rounded-[10px] text-sm font-medium text-tsl08-mut no-underline transition-colors duration-200 hover:text-tsl08-ink hover:bg-tsl08-hover focus-visible:outline-2 focus-visible:outline-tsl08-accent focus-visible:outline-offset-2">Tags</a>
      <a href="#" class="flex items-center min-h-11 px-3.5 rounded-[10px] text-sm font-medium text-tsl08-mut no-underline transition-colors duration-200 hover:text-tsl08-ink hover:bg-tsl08-hover focus-visible:outline-2 focus-visible:outline-tsl08-accent focus-visible:outline-offset-2">Archive</a>
      <a href="#" class="flex items-center min-h-11 px-3.5 rounded-[10px] text-sm font-medium text-tsl08-mut no-underline transition-colors duration-200 hover:text-tsl08-ink hover:bg-tsl08-hover focus-visible:outline-2 focus-visible:outline-tsl08-accent focus-visible:outline-offset-2">Trash</a>
    </nav>
  </aside>
  <main class="p-7 overflow-y-auto [scrollbar-gutter:stable]">
    <h1 class="text-[22px] font-semibold leading-tight mb-2.5">All notes</h1>
    <p class="max-w-[46ch] text-sm leading-relaxed text-tsl08-mut text-pretty">Open the drawer &mdash; the only animated property is transform, so the slide is fully GPU-composited. Zero JavaScript on this page.</p>
  </main>
</section>
```
## CSS
```css
@import "tailwindcss";

@theme {
  --color-tsl08-bg: #16121b;
  --color-tsl08-bg: oklch(0.14 0.015 300);
  --color-tsl08-panel: #1d1824;
  --color-tsl08-panel: oklch(0.18 0.02 300);
  --color-tsl08-ink: #eae7ef;
  --color-tsl08-ink: oklch(0.93 0.005 300);
  --color-tsl08-mut: #9b95a4;
  --color-tsl08-mut: oklch(0.64 0.012 300);
  --color-tsl08-line: #3c3545;
  --color-tsl08-line: oklch(0.29 0.02 300);
  --color-tsl08-accent: #d07ae0;
  --color-tsl08-accent: oklch(0.72 0.16 320);
  --color-tsl08-hover: rgb(234 231 239 / 0.07);
  --color-tsl08-hover: color-mix(in oklab, oklch(0.93 0.005 300) 7%, transparent);
  --color-tsl08-tint: rgb(208 122 224 / 0.18);
  --color-tsl08-tint: color-mix(in oklab, oklch(0.72 0.16 320) 18%, transparent);
  --font-tsl08-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}

.tsl-08 {
  width: 100%;
  height: 100svh;
  min-height: 100svh;
  color-scheme: dark;
}
/* STATE — one checkbox, four consequences: drawer, scrim and the three bars. */

#tsl08-t:checked ~ .tsl-08-drawer {
  translate: 0;
  visibility: visible;
}

#tsl08-t:checked ~ .tsl-08-scrim {
  opacity: 1;
  pointer-events: auto;
}

#tsl08-t:checked ~ header .tsl-08-b1 {
  rotate: 45deg;
  translate: 0 6.5px;
}

#tsl08-t:checked ~ header .tsl-08-b2 {
  opacity: 0;
}

#tsl08-t:checked ~ header .tsl-08-b3 {
  rotate: -45deg;
  translate: 0 -6.5px;
}

#tsl08-t:focus-visible ~ header label[for="tsl08-t"] {
  outline: 2px solid var(--color-tsl08-accent);
  outline-offset: 2px;
}
/* visibility must lag the slide on close and lead it on open — one property
   with two different timings, which no single utility can express. */

.tsl-08 aside {
  transition-delay: 0s, 320ms;
}

.tsl-08:has(#tsl08-t:checked) aside {
  transition-delay: 0s, 0s;
}
```

How this works

One state signal, four consequences. The drawer, the scrim and the three hamburger bars are all SIBLINGS of the checkbox, so no variant reaches them — has-checked: needs the input inside the element, and peer-* cannot descend into the header to find the bars. Five lines of #tsl08-t:checked ~ … do the whole state machine, and every resting value stays a utility. The CSS original needed five descendant selectors rooted at the section; here nothing is rooted anywhere — each element reads the checkbox itself.

Performance is the point of the pattern. Only transform and opacity animate, so the slide is fully compositor-side. visibility rides along with a transition delay on close, which keeps hidden links out of the tab order without the display-toggle that would kill the animation.

Make it yours

  • Right-hand drawer: translate-x-full as the resting state and right-0.
  • Spring feel: ease-[linear(0,1.2_60%,1)] — CSS linear() easing gives a real overshoot.
  • Width: w-[min(280px,85vw)] never swallows a small screen.

Gotchas — read before shipping

  • has-checked: matches only when the checkbox is INSIDE that element — on the shell it works, on a sibling drawer it silently never matches. Everything below the root reads the input by id instead: [:has(#tsl08-t:checked)_&]:translate-x-0.
  • A transform-hidden drawer is still in the tab order; the invisible pair is not optional.
  • Scrim-as-label is a click target, not a button. Esc-to-close needs four lines of JS — document the trade-off rather than pretending it is covered.

Browser support

ChromeSafariFirefoxEdge
117+17.4+129+117+

Floor set by :has(), oklch(), color-mix(), transition-behavior as this demo is written. The core technique itself goes back further — Chrome 111+.

:has() is the only real gate — every state variant here compiles to it. Where :has() is unavailable, a sibling-combinator port (input immediately before the drawer, peer-checked:) runs a decade back.

Techniques used in this demo

Utilitiesfirst-class utility or variant

Search CodeFronts

Loading…