30 Tailwind CSS Sidebar Layouts
26 / 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 Onboarding Wizard Sidebar with Step Progress Tracking

The setup-wizard rail: done / current / upcoming steps, checkmark badges, a connector that fills with accent behind you, and CSS counters numbering the pending steps automatically. Add or remove a step and numbering, line and states all recompute.

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-26 w-full h-svh min-h-svh grid grid-cols-[300px_1fr] font-tsl26-sans bg-tsl26-bg text-tsl26-ink">
  <aside aria-label="Setup progress" class="bg-tsl26-panel border-r border-tsl26-line px-[22px] py-[26px] overflow-y-auto [scrollbar-gutter:stable]">
    <h2 class="mb-6 text-[15px] font-bold tracking-tight">Workspace setup</h2>
    <ol class="tsl-26-steps list-none m-0 p-0 flex flex-col [counter-reset:tsl26]">
      <li data-state="done" class="relative grid grid-cols-[28px_1fr] gap-3.5 pb-7 [counter-increment:tsl26] after:content-[''] after:absolute after:left-[13.5px] after:top-8 after:bottom-1 after:w-0.5 after:rounded-sm after:bg-tsl26-line after:z-0 last:after:hidden last:pb-0 data-[state=done]:after:bg-tsl26-accent">
        <span aria-hidden="true" class="tsl-26-badge relative z-[1] grid place-items-center w-7 h-7 rounded-full border-2 border-tsl26-accent bg-tsl26-accent text-xs font-bold"></span>
        <div><strong class="block text-[13.5px] font-semibold leading-snug">Create account</strong><small class="text-xs leading-[1.45] text-tsl26-mut">ada@nimbus.io verified</small></div>
      </li>
      <li data-state="done" class="relative grid grid-cols-[28px_1fr] gap-3.5 pb-7 [counter-increment:tsl26] after:content-[''] after:absolute after:left-[13.5px] after:top-8 after:bottom-1 after:w-0.5 after:rounded-sm after:bg-tsl26-line after:z-0 last:after:hidden last:pb-0 data-[state=done]:after:bg-tsl26-accent">
        <span aria-hidden="true" class="tsl-26-badge relative z-[1] grid place-items-center w-7 h-7 rounded-full border-2 border-tsl26-accent bg-tsl26-accent text-xs font-bold"></span>
        <div><strong class="block text-[13.5px] font-semibold leading-snug">Invite your team</strong><small class="text-xs leading-[1.45] text-tsl26-mut">4 teammates joined</small></div>
      </li>
      <li data-state="current" aria-current="step" class="relative grid grid-cols-[28px_1fr] gap-3.5 pb-7 [counter-increment:tsl26] after:content-[''] after:absolute after:left-[13.5px] after:top-8 after:bottom-1 after:w-0.5 after:rounded-sm after:bg-tsl26-line after:z-0 last:after:hidden last:pb-0">
        <span aria-hidden="true" class="tsl-26-badge relative z-[1] grid place-items-center w-7 h-7 rounded-full border-2 border-tsl26-accent bg-white text-xs font-bold text-tsl26-accent shadow-[0_0_0_4px_var(--color-tsl26-halo)] before:content-[counter(tsl26)]"></span>
        <div><strong class="block text-[13.5px] font-semibold leading-snug text-tsl26-accent">Connect data source</strong><small class="text-xs leading-[1.45] text-tsl26-mut">Postgres, BigQuery or CSV</small></div>
      </li>
      <li data-state="todo" class="relative grid grid-cols-[28px_1fr] gap-3.5 pb-7 [counter-increment:tsl26] after:content-[''] after:absolute after:left-[13.5px] after:top-8 after:bottom-1 after:w-0.5 after:rounded-sm after:bg-tsl26-line after:z-0 last:after:hidden last:pb-0">
        <span aria-hidden="true" class="tsl-26-badge relative z-[1] grid place-items-center w-7 h-7 rounded-full border-2 border-tsl26-line bg-white text-xs font-bold text-tsl26-mut before:content-[counter(tsl26)]"></span>
        <div><strong class="block text-[13.5px] font-semibold leading-snug text-tsl26-mut">Build first dashboard</strong><small class="text-xs leading-[1.45] text-tsl26-mut">From a template or scratch</small></div>
      </li>
      <li data-state="todo" class="relative grid grid-cols-[28px_1fr] gap-3.5 pb-7 [counter-increment:tsl26] after:content-[''] after:absolute after:left-[13.5px] after:top-8 after:bottom-1 after:w-0.5 after:rounded-sm after:bg-tsl26-line after:z-0 last:after:hidden last:pb-0">
        <span aria-hidden="true" class="tsl-26-badge relative z-[1] grid place-items-center w-7 h-7 rounded-full border-2 border-tsl26-line bg-white text-xs font-bold text-tsl26-mut before:content-[counter(tsl26)]"></span>
        <div><strong class="block text-[13.5px] font-semibold leading-snug text-tsl26-mut">Set up alerts</strong><small class="text-xs leading-[1.45] text-tsl26-mut">Slack or email digests</small></div>
      </li>
    </ol>
  </aside>
  <main class="p-9 overflow-y-auto [scrollbar-gutter:stable]">
    <h1 class="mb-2.5 text-[22px] font-bold leading-tight">Connect data source</h1>
    <p class="max-w-[52ch] mb-[22px] text-sm leading-[1.65] text-tsl26-mut text-pretty">The rail is an ordered list with three data-states &mdash; counters number the todos, checkmarks replace done numbers, and the connector fills behind you.</p>
    <button type="button" class="min-h-12 px-[22px] rounded-[11px] border-0 bg-tsl26-accent text-white text-sm font-semibold cursor-pointer transition-[filter] duration-200 hover:brightness-110 focus-visible:outline-2 focus-visible:outline-tsl26-accent focus-visible:outline-offset-[3px]">Connect Postgres</button>
  </main>
</section>
@import "tailwindcss";

@theme {
  --color-tsl26-bg: #fcfcfe;
  --color-tsl26-bg: oklch(0.99 0.002 275);
  --color-tsl26-panel: #f3f3f7;
  --color-tsl26-panel: oklch(0.965 0.006 275);
  --color-tsl26-ink: #3a3b46;
  --color-tsl26-ink: oklch(0.26 0.02 275);
  --color-tsl26-mut: #6f7180;
  --color-tsl26-mut: oklch(0.5 0.015 275);
  --color-tsl26-line: #dcdce3;
  --color-tsl26-line: oklch(0.88 0.008 275);
  --color-tsl26-accent: #5b4bd6;
  --color-tsl26-accent: oklch(0.55 0.18 285);
  --color-tsl26-halo: rgb(91 75 214 / 0.18);
  --color-tsl26-halo: color-mix(in oklab, oklch(0.55 0.18 285) 18%, transparent);
  --font-tsl26-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}

.tsl-26 {
  width: 100%;
  height: 100svh;
  min-height: 100svh;
  color-scheme: light;
}
/* A masked checkmark replacing generated content: the mask URL is a data
   URI with its own quotes and commas, which no arbitrary value survives. */

.tsl-26 li[data-state="done"] .tsl-26-badge::before {
  content: "";
  width: 12px;
  height: 12px;
  background: #fff;
  -webkit-mask: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round"><path d="m4 12.5 5.5 5.5L20 6.5"/></svg>') center/contain no-repeat;
  mask: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round"><path d="m4 12.5 5.5 5.5L20 6.5"/></svg>') center/contain no-repeat;
}

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-tsl26-bg: #fcfcfe;
  --color-tsl26-bg: oklch(0.99 0.002 275);
  --color-tsl26-panel: #f3f3f7;
  --color-tsl26-panel: oklch(0.965 0.006 275);
  --color-tsl26-ink: #3a3b46;
  --color-tsl26-ink: oklch(0.26 0.02 275);
  --color-tsl26-mut: #6f7180;
  --color-tsl26-mut: oklch(0.5 0.015 275);
  --color-tsl26-line: #dcdce3;
  --color-tsl26-line: oklch(0.88 0.008 275);
  --color-tsl26-accent: #5b4bd6;
  --color-tsl26-accent: oklch(0.55 0.18 285);
  --color-tsl26-halo: rgb(91 75 214 / 0.18);
  --color-tsl26-halo: color-mix(in oklab, oklch(0.55 0.18 285) 18%, transparent);
  --font-tsl26-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}

.tsl-26 {
  width: 100%;
  height: 100svh;
  min-height: 100svh;
  color-scheme: light;
}
/* A masked checkmark replacing generated content: the mask URL is a data
   URI with its own quotes and commas, which no arbitrary value survives. */

.tsl-26 li[data-state="done"] .tsl-26-badge::before {
  content: "";
  width: 12px;
  height: 12px;
  background: #fff;
  -webkit-mask: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round"><path d="m4 12.5 5.5 5.5L20 6.5"/></svg>') center/contain no-repeat;
  mask: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round"><path d="m4 12.5 5.5 5.5L20 6.5"/></svg>') center/contain no-repeat;
}
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.

  • Counters port surprisingly well: [counter-increment:tsl26] and before:content-[counter(tsl26)] are legible arbitrary values, and putting the increment on the list item makes the mechanism obvious for once.
  • The masked checkmark is the exception. A data-URI SVG carries quotes, angle brackets and commas — hostile to arbitrary-value parsing — so it stays a CSS rule. Inlining a real <svg> in the badge is the alternative if you want zero custom CSS.
  • UI change from the CSS original: the primary button is min-h-12, and todo-step titles use the muted token at 4.6:1 rather than inheriting a lighter mix.
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 Onboarding Wizard Sidebar with Step Progress Tracking
Source: https://codefronts.com/layouts/tailwind-css-sidebar-layouts/tailwind-onboarding-wizard-sidebar-step-progress/

The setup-wizard rail: done / current / upcoming steps, checkmark badges, a connector that fills with accent behind you, and CSS counters numbering the pending steps automatically. Add or remove a step and numbering, line and states all recompute.
## HTML
```html
<section class="tsl-26 w-full h-svh min-h-svh grid grid-cols-[300px_1fr] font-tsl26-sans bg-tsl26-bg text-tsl26-ink">
  <aside aria-label="Setup progress" class="bg-tsl26-panel border-r border-tsl26-line px-[22px] py-[26px] overflow-y-auto [scrollbar-gutter:stable]">
    <h2 class="mb-6 text-[15px] font-bold tracking-tight">Workspace setup</h2>
    <ol class="tsl-26-steps list-none m-0 p-0 flex flex-col [counter-reset:tsl26]">
      <li data-state="done" class="relative grid grid-cols-[28px_1fr] gap-3.5 pb-7 [counter-increment:tsl26] after:content-[''] after:absolute after:left-[13.5px] after:top-8 after:bottom-1 after:w-0.5 after:rounded-sm after:bg-tsl26-line after:z-0 last:after:hidden last:pb-0 data-[state=done]:after:bg-tsl26-accent">
        <span aria-hidden="true" class="tsl-26-badge relative z-[1] grid place-items-center w-7 h-7 rounded-full border-2 border-tsl26-accent bg-tsl26-accent text-xs font-bold"></span>
        <div><strong class="block text-[13.5px] font-semibold leading-snug">Create account</strong><small class="text-xs leading-[1.45] text-tsl26-mut">ada@nimbus.io verified</small></div>
      </li>
      <li data-state="done" class="relative grid grid-cols-[28px_1fr] gap-3.5 pb-7 [counter-increment:tsl26] after:content-[''] after:absolute after:left-[13.5px] after:top-8 after:bottom-1 after:w-0.5 after:rounded-sm after:bg-tsl26-line after:z-0 last:after:hidden last:pb-0 data-[state=done]:after:bg-tsl26-accent">
        <span aria-hidden="true" class="tsl-26-badge relative z-[1] grid place-items-center w-7 h-7 rounded-full border-2 border-tsl26-accent bg-tsl26-accent text-xs font-bold"></span>
        <div><strong class="block text-[13.5px] font-semibold leading-snug">Invite your team</strong><small class="text-xs leading-[1.45] text-tsl26-mut">4 teammates joined</small></div>
      </li>
      <li data-state="current" aria-current="step" class="relative grid grid-cols-[28px_1fr] gap-3.5 pb-7 [counter-increment:tsl26] after:content-[''] after:absolute after:left-[13.5px] after:top-8 after:bottom-1 after:w-0.5 after:rounded-sm after:bg-tsl26-line after:z-0 last:after:hidden last:pb-0">
        <span aria-hidden="true" class="tsl-26-badge relative z-[1] grid place-items-center w-7 h-7 rounded-full border-2 border-tsl26-accent bg-white text-xs font-bold text-tsl26-accent shadow-[0_0_0_4px_var(--color-tsl26-halo)] before:content-[counter(tsl26)]"></span>
        <div><strong class="block text-[13.5px] font-semibold leading-snug text-tsl26-accent">Connect data source</strong><small class="text-xs leading-[1.45] text-tsl26-mut">Postgres, BigQuery or CSV</small></div>
      </li>
      <li data-state="todo" class="relative grid grid-cols-[28px_1fr] gap-3.5 pb-7 [counter-increment:tsl26] after:content-[''] after:absolute after:left-[13.5px] after:top-8 after:bottom-1 after:w-0.5 after:rounded-sm after:bg-tsl26-line after:z-0 last:after:hidden last:pb-0">
        <span aria-hidden="true" class="tsl-26-badge relative z-[1] grid place-items-center w-7 h-7 rounded-full border-2 border-tsl26-line bg-white text-xs font-bold text-tsl26-mut before:content-[counter(tsl26)]"></span>
        <div><strong class="block text-[13.5px] font-semibold leading-snug text-tsl26-mut">Build first dashboard</strong><small class="text-xs leading-[1.45] text-tsl26-mut">From a template or scratch</small></div>
      </li>
      <li data-state="todo" class="relative grid grid-cols-[28px_1fr] gap-3.5 pb-7 [counter-increment:tsl26] after:content-[''] after:absolute after:left-[13.5px] after:top-8 after:bottom-1 after:w-0.5 after:rounded-sm after:bg-tsl26-line after:z-0 last:after:hidden last:pb-0">
        <span aria-hidden="true" class="tsl-26-badge relative z-[1] grid place-items-center w-7 h-7 rounded-full border-2 border-tsl26-line bg-white text-xs font-bold text-tsl26-mut before:content-[counter(tsl26)]"></span>
        <div><strong class="block text-[13.5px] font-semibold leading-snug text-tsl26-mut">Set up alerts</strong><small class="text-xs leading-[1.45] text-tsl26-mut">Slack or email digests</small></div>
      </li>
    </ol>
  </aside>
  <main class="p-9 overflow-y-auto [scrollbar-gutter:stable]">
    <h1 class="mb-2.5 text-[22px] font-bold leading-tight">Connect data source</h1>
    <p class="max-w-[52ch] mb-[22px] text-sm leading-[1.65] text-tsl26-mut text-pretty">The rail is an ordered list with three data-states &mdash; counters number the todos, checkmarks replace done numbers, and the connector fills behind you.</p>
    <button type="button" class="min-h-12 px-[22px] rounded-[11px] border-0 bg-tsl26-accent text-white text-sm font-semibold cursor-pointer transition-[filter] duration-200 hover:brightness-110 focus-visible:outline-2 focus-visible:outline-tsl26-accent focus-visible:outline-offset-[3px]">Connect Postgres</button>
  </main>
</section>
```
## CSS
```css
@import "tailwindcss";

@theme {
  --color-tsl26-bg: #fcfcfe;
  --color-tsl26-bg: oklch(0.99 0.002 275);
  --color-tsl26-panel: #f3f3f7;
  --color-tsl26-panel: oklch(0.965 0.006 275);
  --color-tsl26-ink: #3a3b46;
  --color-tsl26-ink: oklch(0.26 0.02 275);
  --color-tsl26-mut: #6f7180;
  --color-tsl26-mut: oklch(0.5 0.015 275);
  --color-tsl26-line: #dcdce3;
  --color-tsl26-line: oklch(0.88 0.008 275);
  --color-tsl26-accent: #5b4bd6;
  --color-tsl26-accent: oklch(0.55 0.18 285);
  --color-tsl26-halo: rgb(91 75 214 / 0.18);
  --color-tsl26-halo: color-mix(in oklab, oklch(0.55 0.18 285) 18%, transparent);
  --font-tsl26-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}

.tsl-26 {
  width: 100%;
  height: 100svh;
  min-height: 100svh;
  color-scheme: light;
}
/* A masked checkmark replacing generated content: the mask URL is a data
   URI with its own quotes and commas, which no arbitrary value survives. */

.tsl-26 li[data-state="done"] .tsl-26-badge::before {
  content: "";
  width: 12px;
  height: 12px;
  background: #fff;
  -webkit-mask: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round"><path d="m4 12.5 5.5 5.5L20 6.5"/></svg>') center/contain no-repeat;
  mask: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round"><path d="m4 12.5 5.5 5.5L20 6.5"/></svg>') center/contain no-repeat;
}
```

How this works

Three states and one counter generate the whole rail, which is why it survives edits that break hand-built wizards. data-[state=…]: variants style badge, connector and text from a single attribute per step; the counter numbers the pending ones; done steps replace their number with a masked checkmark.

The current step gets a halo — shadow-[0_0_0_4px_var(--color-tsl26-halo)] — because in a five-step list the eye needs one unambiguous focal point, and colour alone in the text is too quiet.

Make it yours

  • Steps are data: map state per step server-side; the utilities handle any count.
  • Let users jump back by wrapping done steps' text in links; leave todo steps as plain text so nobody skips ahead.
  • Sub-steps: a nested <ol> with its own [counter-reset:…] namespaces automatically.

Gotchas — read before shipping

  • aria-current="step" belongs on ONE element. Duplicates make a screen reader announce several current steps.
  • The connector needs the item relative and the badge above it (z-[1]), or the line strikes through the checkmark.
  • Never encode done-ness in colour alone — the checkmark replacing the number is the second signal.

Browser support

ChromeSafariFirefoxEdge
111+16.4+128+111+

CSS counters are ancient; color-mix() and the masked checkmark set the practical floor, and an inline SVG is the fallback.

Techniques used in this demo

Utilitiesfirst-class utility or variant

Search CodeFronts

Loading…