30 Tailwind CSS Sidebar Layouts
23 / 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 Sliding Active Indicator Sidebar with CSS :target

The sliding active bar — the effect teams install a motion library for — with zero JavaScript. Links target anchors, :target identifies the section, and one indicator element transitions between stops computed from a single row-height variable.

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-23 w-full h-svh min-h-svh grid grid-cols-[224px_1fr] [--row:46px] font-tsl23-sans bg-tsl23-bg text-tsl23-ink">
  <aside aria-label="Sections" class="relative bg-tsl23-panel border-r border-tsl23-line px-3 py-[18px]">
    <span aria-hidden="true" class="tsl-23-ind absolute left-1 top-[18px] mt-2 w-[3px] h-7 rounded bg-tsl23-accent shadow-[0_0_12px_var(--color-tsl23-glow)] translate-y-0 transition-transform duration-[320ms] ease-[cubic-bezier(.5,0,.2,1.15)] motion-reduce:transition-none"></span>
    <nav class="flex flex-col gap-0.5">
      <a href="#tsl23-inbox" class="flex items-center h-11 px-4 rounded-[10px] text-sm font-medium text-tsl23-mut no-underline transition-colors duration-200 hover:text-tsl23-ink hover:bg-tsl23-hover focus-visible:outline-2 focus-visible:outline-tsl23-accent focus-visible:outline-offset-2">Inbox</a>
      <a href="#tsl23-today" class="flex items-center h-11 px-4 rounded-[10px] text-sm font-medium text-tsl23-mut no-underline transition-colors duration-200 hover:text-tsl23-ink hover:bg-tsl23-hover focus-visible:outline-2 focus-visible:outline-tsl23-accent focus-visible:outline-offset-2">Today</a>
      <a href="#tsl23-upcoming" class="flex items-center h-11 px-4 rounded-[10px] text-sm font-medium text-tsl23-mut no-underline transition-colors duration-200 hover:text-tsl23-ink hover:bg-tsl23-hover focus-visible:outline-2 focus-visible:outline-tsl23-accent focus-visible:outline-offset-2">Upcoming</a>
      <a href="#tsl23-someday" class="flex items-center h-11 px-4 rounded-[10px] text-sm font-medium text-tsl23-mut no-underline transition-colors duration-200 hover:text-tsl23-ink hover:bg-tsl23-hover focus-visible:outline-2 focus-visible:outline-tsl23-accent focus-visible:outline-offset-2">Someday</a>
      <a href="#tsl23-logbook" class="flex items-center h-11 px-4 rounded-[10px] text-sm font-medium text-tsl23-mut no-underline transition-colors duration-200 hover:text-tsl23-ink hover:bg-tsl23-hover focus-visible:outline-2 focus-visible:outline-tsl23-accent focus-visible:outline-offset-2">Logbook</a>
    </nav>
  </aside>
  <div class="overflow-y-auto p-8 grid gap-10 content-start [scrollbar-gutter:stable]">
    <section id="tsl23-inbox" class="scroll-mt-6"><h1 class="mb-2 text-[21px] font-semibold leading-tight">Inbox</h1><p class="max-w-[48ch] text-sm leading-relaxed text-tsl23-mut text-pretty">Click the menu &mdash; the accent bar glides between items. URL fragments plus <code class="font-tsl23-mono text-[12.5px]">:target</code> and <code class="font-tsl23-mono text-[12.5px]">:has()</code>, no script.</p></section>
    <section id="tsl23-today" class="scroll-mt-6"><h1 class="mb-2 text-[21px] font-semibold leading-tight">Today</h1><p class="max-w-[48ch] text-sm leading-relaxed text-tsl23-mut text-pretty">Three tasks scheduled.</p></section>
    <section id="tsl23-upcoming" class="scroll-mt-6"><h1 class="mb-2 text-[21px] font-semibold leading-tight">Upcoming</h1><p class="max-w-[48ch] text-sm leading-relaxed text-tsl23-mut text-pretty">Planning horizon: two weeks.</p></section>
    <section id="tsl23-someday" class="scroll-mt-6"><h1 class="mb-2 text-[21px] font-semibold leading-tight">Someday</h1><p class="max-w-[48ch] text-sm leading-relaxed text-tsl23-mut text-pretty">Ideas parked without guilt.</p></section>
    <section id="tsl23-logbook" class="scroll-mt-6"><h1 class="mb-2 text-[21px] font-semibold leading-tight">Logbook</h1><p class="max-w-[48ch] text-sm leading-relaxed text-tsl23-mut text-pretty">Completed items, newest first.</p></section>
  </div>
</section>
@import "tailwindcss";

@theme {
  --color-tsl23-bg: #101613;
  --color-tsl23-bg: oklch(0.13 0.012 145);
  --color-tsl23-panel: #161f1a;
  --color-tsl23-panel: oklch(0.17 0.015 145);
  --color-tsl23-ink: #e8f0ea;
  --color-tsl23-ink: oklch(0.94 0.005 145);
  --color-tsl23-mut: #929d95;
  --color-tsl23-mut: oklch(0.64 0.012 145);
  --color-tsl23-line: #333e37;
  --color-tsl23-line: oklch(0.28 0.016 145);
  --color-tsl23-accent: #67e29a;
  --color-tsl23-accent: oklch(0.8 0.16 150);
  --color-tsl23-glow: rgb(103 226 154 / 0.55);
  --color-tsl23-glow: color-mix(in oklab, oklch(0.8 0.16 150) 55%, transparent);
  --color-tsl23-hover: rgb(232 240 234 / 0.06);
  --color-tsl23-hover: color-mix(in oklab, oklch(0.94 0.005 145) 6%, transparent);
  --color-tsl23-tint: rgb(103 226 154 / 0.12);
  --color-tsl23-tint: color-mix(in oklab, oklch(0.8 0.16 150) 12%, transparent);
  --font-tsl23-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
  --font-tsl23-mono: 'JetBrains Mono', ui-monospace, monospace;
}

.tsl-23 {
  width: 100%;
  height: 100svh;
  min-height: 100svh;
  color-scheme: dark;
}
/* Nothing is :target on first paint — park the resting highlight on item one
   so the default state looks intentional rather than empty. */

.tsl-23:not(:has(:target)) nav a:first-child {
  color: var(--color-tsl23-ink);
  background: var(--color-tsl23-tint);
}
/* STATE — indicator stops and the matching highlight, both derived from --row. */

.tsl-23:has(#tsl23-today:target) .tsl-23-ind {
  translate: 0 var(--row);
}

.tsl-23:has(#tsl23-upcoming:target) .tsl-23-ind {
  translate: 0 calc(var(--row) * 2);
}

.tsl-23:has(#tsl23-someday:target) .tsl-23-ind {
  translate: 0 calc(var(--row) * 3);
}

.tsl-23:has(#tsl23-logbook:target) .tsl-23-ind {
  translate: 0 calc(var(--row) * 4);
}

.tsl-23:has(#tsl23-inbox:target) a[href="#tsl23-inbox"],
.tsl-23:has(#tsl23-today:target) a[href="#tsl23-today"],
.tsl-23:has(#tsl23-upcoming:target) a[href="#tsl23-upcoming"],
.tsl-23:has(#tsl23-someday:target) a[href="#tsl23-someday"],
.tsl-23:has(#tsl23-logbook:target) a[href="#tsl23-logbook"] {
  color: var(--color-tsl23-ink);
  background: var(--color-tsl23-tint);
}

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-tsl23-bg: #101613;
  --color-tsl23-bg: oklch(0.13 0.012 145);
  --color-tsl23-panel: #161f1a;
  --color-tsl23-panel: oklch(0.17 0.015 145);
  --color-tsl23-ink: #e8f0ea;
  --color-tsl23-ink: oklch(0.94 0.005 145);
  --color-tsl23-mut: #929d95;
  --color-tsl23-mut: oklch(0.64 0.012 145);
  --color-tsl23-line: #333e37;
  --color-tsl23-line: oklch(0.28 0.016 145);
  --color-tsl23-accent: #67e29a;
  --color-tsl23-accent: oklch(0.8 0.16 150);
  --color-tsl23-glow: rgb(103 226 154 / 0.55);
  --color-tsl23-glow: color-mix(in oklab, oklch(0.8 0.16 150) 55%, transparent);
  --color-tsl23-hover: rgb(232 240 234 / 0.06);
  --color-tsl23-hover: color-mix(in oklab, oklch(0.94 0.005 145) 6%, transparent);
  --color-tsl23-tint: rgb(103 226 154 / 0.12);
  --color-tsl23-tint: color-mix(in oklab, oklch(0.8 0.16 150) 12%, transparent);
  --font-tsl23-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
  --font-tsl23-mono: 'JetBrains Mono', ui-monospace, monospace;
}

.tsl-23 {
  width: 100%;
  height: 100svh;
  min-height: 100svh;
  color-scheme: dark;
}
/* Nothing is :target on first paint — park the resting highlight on item one
   so the default state looks intentional rather than empty. */

.tsl-23:not(:has(:target)) nav a:first-child {
  color: var(--color-tsl23-ink);
  background: var(--color-tsl23-tint);
}
/* STATE — indicator stops and the matching highlight, both derived from --row. */

.tsl-23:has(#tsl23-today:target) .tsl-23-ind {
  translate: 0 var(--row);
}

.tsl-23:has(#tsl23-upcoming:target) .tsl-23-ind {
  translate: 0 calc(var(--row) * 2);
}

.tsl-23:has(#tsl23-someday:target) .tsl-23-ind {
  translate: 0 calc(var(--row) * 3);
}

.tsl-23:has(#tsl23-logbook:target) .tsl-23-ind {
  translate: 0 calc(var(--row) * 4);
}

.tsl-23:has(#tsl23-inbox:target) a[href="#tsl23-inbox"],
.tsl-23:has(#tsl23-today:target) a[href="#tsl23-today"],
.tsl-23:has(#tsl23-upcoming:target) a[href="#tsl23-upcoming"],
.tsl-23:has(#tsl23-someday:target) a[href="#tsl23-someday"],
.tsl-23:has(#tsl23-logbook:target) a[href="#tsl23-logbook"] {
  color: var(--color-tsl23-ink);
  background: var(--color-tsl23-tint);
}
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.

  • A six-selector comma chain in the CSS original becomes one has-[#id:target]: pair per link. The rule that highlights a link now lives on that link, which is the same win as demo 03 — and here it also removes the need to keep the chain in sync with the indicator stops.
  • UI change from the CSS original: rows are 44px exactly (the original's 44px height plus 2px gap is now the documented --row contract), and the indicator's glow moved to a token so the shadow's commas stay readable.
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 Sliding Active Indicator Sidebar with CSS :target
Source: https://codefronts.com/layouts/tailwind-css-sidebar-layouts/tailwind-sliding-active-indicator-sidebar-target/

The sliding active bar &mdash; the effect teams install a motion library for &mdash; with zero JavaScript. Links target anchors, :target identifies the section, and one indicator element transitions between stops computed from a single row-height variable.
## HTML
```html
<section class="tsl-23 w-full h-svh min-h-svh grid grid-cols-[224px_1fr] [--row:46px] font-tsl23-sans bg-tsl23-bg text-tsl23-ink">
  <aside aria-label="Sections" class="relative bg-tsl23-panel border-r border-tsl23-line px-3 py-[18px]">
    <span aria-hidden="true" class="tsl-23-ind absolute left-1 top-[18px] mt-2 w-[3px] h-7 rounded bg-tsl23-accent shadow-[0_0_12px_var(--color-tsl23-glow)] translate-y-0 transition-transform duration-[320ms] ease-[cubic-bezier(.5,0,.2,1.15)] motion-reduce:transition-none"></span>
    <nav class="flex flex-col gap-0.5">
      <a href="#tsl23-inbox" class="flex items-center h-11 px-4 rounded-[10px] text-sm font-medium text-tsl23-mut no-underline transition-colors duration-200 hover:text-tsl23-ink hover:bg-tsl23-hover focus-visible:outline-2 focus-visible:outline-tsl23-accent focus-visible:outline-offset-2">Inbox</a>
      <a href="#tsl23-today" class="flex items-center h-11 px-4 rounded-[10px] text-sm font-medium text-tsl23-mut no-underline transition-colors duration-200 hover:text-tsl23-ink hover:bg-tsl23-hover focus-visible:outline-2 focus-visible:outline-tsl23-accent focus-visible:outline-offset-2">Today</a>
      <a href="#tsl23-upcoming" class="flex items-center h-11 px-4 rounded-[10px] text-sm font-medium text-tsl23-mut no-underline transition-colors duration-200 hover:text-tsl23-ink hover:bg-tsl23-hover focus-visible:outline-2 focus-visible:outline-tsl23-accent focus-visible:outline-offset-2">Upcoming</a>
      <a href="#tsl23-someday" class="flex items-center h-11 px-4 rounded-[10px] text-sm font-medium text-tsl23-mut no-underline transition-colors duration-200 hover:text-tsl23-ink hover:bg-tsl23-hover focus-visible:outline-2 focus-visible:outline-tsl23-accent focus-visible:outline-offset-2">Someday</a>
      <a href="#tsl23-logbook" class="flex items-center h-11 px-4 rounded-[10px] text-sm font-medium text-tsl23-mut no-underline transition-colors duration-200 hover:text-tsl23-ink hover:bg-tsl23-hover focus-visible:outline-2 focus-visible:outline-tsl23-accent focus-visible:outline-offset-2">Logbook</a>
    </nav>
  </aside>
  <div class="overflow-y-auto p-8 grid gap-10 content-start [scrollbar-gutter:stable]">
    <section id="tsl23-inbox" class="scroll-mt-6"><h1 class="mb-2 text-[21px] font-semibold leading-tight">Inbox</h1><p class="max-w-[48ch] text-sm leading-relaxed text-tsl23-mut text-pretty">Click the menu &mdash; the accent bar glides between items. URL fragments plus <code class="font-tsl23-mono text-[12.5px]">:target</code> and <code class="font-tsl23-mono text-[12.5px]">:has()</code>, no script.</p></section>
    <section id="tsl23-today" class="scroll-mt-6"><h1 class="mb-2 text-[21px] font-semibold leading-tight">Today</h1><p class="max-w-[48ch] text-sm leading-relaxed text-tsl23-mut text-pretty">Three tasks scheduled.</p></section>
    <section id="tsl23-upcoming" class="scroll-mt-6"><h1 class="mb-2 text-[21px] font-semibold leading-tight">Upcoming</h1><p class="max-w-[48ch] text-sm leading-relaxed text-tsl23-mut text-pretty">Planning horizon: two weeks.</p></section>
    <section id="tsl23-someday" class="scroll-mt-6"><h1 class="mb-2 text-[21px] font-semibold leading-tight">Someday</h1><p class="max-w-[48ch] text-sm leading-relaxed text-tsl23-mut text-pretty">Ideas parked without guilt.</p></section>
    <section id="tsl23-logbook" class="scroll-mt-6"><h1 class="mb-2 text-[21px] font-semibold leading-tight">Logbook</h1><p class="max-w-[48ch] text-sm leading-relaxed text-tsl23-mut text-pretty">Completed items, newest first.</p></section>
  </div>
</section>
```
## CSS
```css
@import "tailwindcss";

@theme {
  --color-tsl23-bg: #101613;
  --color-tsl23-bg: oklch(0.13 0.012 145);
  --color-tsl23-panel: #161f1a;
  --color-tsl23-panel: oklch(0.17 0.015 145);
  --color-tsl23-ink: #e8f0ea;
  --color-tsl23-ink: oklch(0.94 0.005 145);
  --color-tsl23-mut: #929d95;
  --color-tsl23-mut: oklch(0.64 0.012 145);
  --color-tsl23-line: #333e37;
  --color-tsl23-line: oklch(0.28 0.016 145);
  --color-tsl23-accent: #67e29a;
  --color-tsl23-accent: oklch(0.8 0.16 150);
  --color-tsl23-glow: rgb(103 226 154 / 0.55);
  --color-tsl23-glow: color-mix(in oklab, oklch(0.8 0.16 150) 55%, transparent);
  --color-tsl23-hover: rgb(232 240 234 / 0.06);
  --color-tsl23-hover: color-mix(in oklab, oklch(0.94 0.005 145) 6%, transparent);
  --color-tsl23-tint: rgb(103 226 154 / 0.12);
  --color-tsl23-tint: color-mix(in oklab, oklch(0.8 0.16 150) 12%, transparent);
  --font-tsl23-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
  --font-tsl23-mono: 'JetBrains Mono', ui-monospace, monospace;
}

.tsl-23 {
  width: 100%;
  height: 100svh;
  min-height: 100svh;
  color-scheme: dark;
}
/* Nothing is :target on first paint — park the resting highlight on item one
   so the default state looks intentional rather than empty. */

.tsl-23:not(:has(:target)) nav a:first-child {
  color: var(--color-tsl23-ink);
  background: var(--color-tsl23-tint);
}
/* STATE — indicator stops and the matching highlight, both derived from --row. */

.tsl-23:has(#tsl23-today:target) .tsl-23-ind {
  translate: 0 var(--row);
}

.tsl-23:has(#tsl23-upcoming:target) .tsl-23-ind {
  translate: 0 calc(var(--row) * 2);
}

.tsl-23:has(#tsl23-someday:target) .tsl-23-ind {
  translate: 0 calc(var(--row) * 3);
}

.tsl-23:has(#tsl23-logbook:target) .tsl-23-ind {
  translate: 0 calc(var(--row) * 4);
}

.tsl-23:has(#tsl23-inbox:target) a[href="#tsl23-inbox"],
.tsl-23:has(#tsl23-today:target) a[href="#tsl23-today"],
.tsl-23:has(#tsl23-upcoming:target) a[href="#tsl23-upcoming"],
.tsl-23:has(#tsl23-someday:target) a[href="#tsl23-someday"],
.tsl-23:has(#tsl23-logbook:target) a[href="#tsl23-logbook"] {
  color: var(--color-tsl23-ink);
  background: var(--color-tsl23-tint);
}
```

How this works

Fixed row height is the contract. Rows are exactly 44px with a 2px gap, so stop n is n × var(--row) — knowable constants rather than measured values. That is the honest limitation too: this pattern fits stable menus, while dynamic lists want demo 14's anchor positioning, which measures for you.

The default state needs stating. With no fragment in the URL, nothing is :target. one scoped :not(:has(:target)) rule in the stylesheet handles it — a negated relational variant would have to be repeated on every link to say the same thing.

Make it yours

  • Extract --row and every stop follows; change row height in one place.
  • Horizontal variant: the same stops on translate-x under a tab bar.
  • Prefer a pill? Swap the 3px bar for a full-row rounded rect at low opacity behind the links.

Gotchas — read before shipping

  • Every :target jump scrolls the content pane — that is the feature, but keep the pane in its own scroll context so the page does not jump too.
  • Ids are document-global; namespace them or two widgets will fight over the fragment.
  • Handle the no-fragment state explicitly or the indicator is invisible until the first click.

Browser support

ChromeSafariFirefoxEdge
111+16.4+128+111+

:has() containing :target is the gate (Baseline Dec 2023). Without it links still highlight on :target; only the glide is lost.

Techniques used in this demo

Utilitiesfirst-class utility or variant

Theme tokensset in @theme, used by a utility

Search CodeFronts

Loading…