03 / 30
All 30 in 30 Tailwind CSS Sidebar Layouts
- 01Tailwind SaaS Dashboard Sidebar with Native Dark Mode Switch
- 02Tailwind Collapsible Admin Sidebar with Icon Tooltips
- 03Tailwind Documentation Sidebar with Multi-Level :target Tree
- 04Tailwind Responsive Sidebar to Bottom Navigation Switch Layout
- 05Tailwind Analytics Sidebar with CSS Subgrid Column Alignment
- 06Tailwind Animated Collapsible Sidebar with Icon-Only Mode
- 07Tailwind Sticky Sidebar Layout with Scroll-Driven Progress Bar
- 08Tailwind Pure CSS Collapsible Sidebar Drawer without JavaScript
- 09Tailwind Fixed Sidebar Layout with Independently Scrolling Content
- 10Tailwind Off-Canvas Navigation Drawer with the Popover API
- 11Tailwind Right-Aligned Sidebar Layout for Inspector Panels
- 12Tailwind Two-Pane Webmail Layout with List and Reading Pane
- 13Tailwind Dual Sidebar App Shell with Left Nav and Context Panel
- 14Tailwind Mini Icon Rail Sidebar with Anchor-Positioned Active Pill
- 15Tailwind E-Commerce Filter Sidebar with Native Details Accordion
- 16Tailwind Floating Glassmorphism Sidebar with Gradient Hairline
- 17Tailwind Sidebar Menu with Pinned User Profile Footer
- 18Tailwind Push-Content Sidebar Drawer with GPU Transitions
- 19Tailwind Off-Canvas Cart Overlay with Blurred Backdrop Scrim
- 20Tailwind Sidebar with Persistent Collapsed State in localStorage
- 21Tailwind Sidebar with Sticky Search Bar Inside the Rail
- 22Tailwind Section-Grouped Sidebar Navigation with Header Labels
- 23Tailwind Sliding Active Indicator Sidebar with CSS :target
- 24Tailwind Multi-Level Nested Tree Sidebar with Details Branches
- 25Tailwind Tabbed Sidebar with Radio-Driven Panel Switching
- 26Tailwind Onboarding Wizard Sidebar with Step Progress Tracking
- 27Tailwind Resizable Sidebar Layout Using the CSS Resize Property
- 28Tailwind Three-Pane Workspace Shell with Icon Rail and List
- 29Tailwind Adaptive Sidebar Layout Using CSS Container Queries
- 30Tailwind Frosted Glass Floating Sidebar with Animated Aurora
Tailwind Documentation Sidebar with Multi-Level :target Tree
The Stripe-style docs tree: three levels of nested navigation, a guide rail connecting each level, and the current section lit automatically as readers jump between anchors — no scroll-spy script. The port turns eight :has(#id:target) selectors into eight arbitrary variants that sit on the links they light.
Mosaic by Cruip: a complete Tailwind admin dashboard template in HTML, React, Next.js and Vue · $69
Cruip · Mosaic by Cruip · Affiliate link
The code
<section class="tsl-03 w-full h-svh min-h-svh grid grid-cols-[250px_1fr] font-tsl03-sans bg-tsl03-bg text-tsl03-ink">
<aside class="border-r border-tsl03-line px-3 py-6 overflow-y-auto [scrollbar-gutter:stable]">
<nav aria-label="Documentation">
<ul class="list-none m-0 p-0 [--depth:0]">
<li><a href="#tsl03-start" class="block my-px py-[7px] pe-2.5 ps-[calc(0.75rem+var(--depth)*0.875rem)] rounded-lg text-[13.5px] font-medium leading-tight text-tsl03-mut no-underline transition-colors duration-150 hover:text-tsl03-ink hover:bg-tsl03-hover focus-visible:outline-2 focus-visible:outline-tsl03-accent focus-visible:outline-offset-1">Getting started</a>
<ul class="list-none m-0 p-0 [--depth:1] border-s border-tsl03-line ms-[19px]">
<li><a href="#tsl03-install" class="block my-px py-[7px] pe-2.5 ps-3 rounded-lg text-[13.5px] font-medium leading-tight text-tsl03-mut no-underline transition-colors duration-150 hover:text-tsl03-ink hover:bg-tsl03-hover focus-visible:outline-2 focus-visible:outline-tsl03-accent focus-visible:outline-offset-1">Installation</a></li>
<li><a href="#tsl03-auth" class="block my-px py-[7px] pe-2.5 ps-3 rounded-lg text-[13.5px] font-medium leading-tight text-tsl03-mut no-underline transition-colors duration-150 hover:text-tsl03-ink hover:bg-tsl03-hover focus-visible:outline-2 focus-visible:outline-tsl03-accent focus-visible:outline-offset-1">Authentication</a>
<ul class="list-none m-0 p-0 [--depth:2] border-s border-tsl03-line ms-[15px]">
<li><a href="#tsl03-keys" class="block my-px py-[7px] pe-2.5 ps-3 rounded-lg text-[13.5px] font-medium leading-tight text-tsl03-mut no-underline transition-colors duration-150 hover:text-tsl03-ink hover:bg-tsl03-hover focus-visible:outline-2 focus-visible:outline-tsl03-accent focus-visible:outline-offset-1">API keys</a></li>
<li><a href="#tsl03-oauth" class="block my-px py-[7px] pe-2.5 ps-3 rounded-lg text-[13.5px] font-medium leading-tight text-tsl03-mut no-underline transition-colors duration-150 hover:text-tsl03-ink hover:bg-tsl03-hover focus-visible:outline-2 focus-visible:outline-tsl03-accent focus-visible:outline-offset-1">OAuth 2.0</a></li>
</ul>
</li>
</ul>
</li>
<li class="mt-1"><a href="#tsl03-core" class="block my-px py-[7px] pe-2.5 ps-[calc(0.75rem+var(--depth)*0.875rem)] rounded-lg text-[13.5px] font-medium leading-tight text-tsl03-mut no-underline transition-colors duration-150 hover:text-tsl03-ink hover:bg-tsl03-hover focus-visible:outline-2 focus-visible:outline-tsl03-accent focus-visible:outline-offset-1">Core concepts</a>
<ul class="list-none m-0 p-0 [--depth:1] border-s border-tsl03-line ms-[19px]">
<li><a href="#tsl03-webhooks" class="block my-px py-[7px] pe-2.5 ps-3 rounded-lg text-[13.5px] font-medium leading-tight text-tsl03-mut no-underline transition-colors duration-150 hover:text-tsl03-ink hover:bg-tsl03-hover focus-visible:outline-2 focus-visible:outline-tsl03-accent focus-visible:outline-offset-1">Webhooks</a></li>
<li><a href="#tsl03-errors" class="block my-px py-[7px] pe-2.5 ps-3 rounded-lg text-[13.5px] font-medium leading-tight text-tsl03-mut no-underline transition-colors duration-150 hover:text-tsl03-ink hover:bg-tsl03-hover focus-visible:outline-2 focus-visible:outline-tsl03-accent focus-visible:outline-offset-1">Error handling</a></li>
</ul>
</li>
</ul>
</nav>
</aside>
<main class="overflow-y-auto scroll-smooth px-11 py-9 grid gap-[34px] content-start motion-reduce:scroll-auto [scrollbar-gutter:stable]">
<section id="tsl03-start" class="scroll-mt-6"><h1 class="text-[26px] font-bold leading-tight tracking-tight">Getting started</h1><p class="mt-2 max-w-[58ch] text-sm leading-[1.65] text-tsl03-mut text-pretty">Click any sidebar item — the matching link lights via <code class="font-tsl03-mono text-[12.5px]">:target</code>, no scroll-spy script.</p></section>
<section id="tsl03-install" class="scroll-mt-6"><h2 class="text-xl font-bold leading-tight">Installation</h2><p class="mt-2 max-w-[58ch] text-sm leading-[1.65] text-tsl03-mut text-pretty">Install the SDK with your package manager of choice.</p></section>
<section id="tsl03-auth" class="scroll-mt-6"><h2 class="text-xl font-bold leading-tight">Authentication</h2><p class="mt-2 max-w-[58ch] text-sm leading-[1.65] text-tsl03-mut text-pretty">All requests are authenticated with a bearer token.</p></section>
<section id="tsl03-keys" class="scroll-mt-6"><h3 class="text-base font-semibold leading-snug">API keys</h3><p class="mt-2 max-w-[58ch] text-sm leading-[1.65] text-tsl03-mut text-pretty">Generate keys from the dashboard. Rotate them quarterly.</p></section>
<section id="tsl03-oauth" class="scroll-mt-6"><h3 class="text-base font-semibold leading-snug">OAuth 2.0</h3><p class="mt-2 max-w-[58ch] text-sm leading-[1.65] text-tsl03-mut text-pretty">Use the authorization-code flow for user-scoped access.</p></section>
<section id="tsl03-core" class="scroll-mt-6"><h2 class="text-xl font-bold leading-tight">Core concepts</h2><p class="mt-2 max-w-[58ch] text-sm leading-[1.65] text-tsl03-mut text-pretty">Resources, events and idempotency.</p></section>
<section id="tsl03-webhooks" class="scroll-mt-6"><h3 class="text-base font-semibold leading-snug">Webhooks</h3><p class="mt-2 max-w-[58ch] text-sm leading-[1.65] text-tsl03-mut text-pretty">Receive events at your endpoint within seconds.</p></section>
<section id="tsl03-errors" class="scroll-mt-6"><h3 class="text-base font-semibold leading-snug">Error handling</h3><p class="mt-2 max-w-[58ch] text-sm leading-[1.65] text-tsl03-mut text-pretty">Every error returns a typed code and a human message.</p></section>
</main>
</section><section class="tsl-03 w-full h-svh min-h-svh grid grid-cols-[250px_1fr] font-tsl03-sans bg-tsl03-bg text-tsl03-ink">
<aside class="border-r border-tsl03-line px-3 py-6 overflow-y-auto [scrollbar-gutter:stable]">
<nav aria-label="Documentation">
<ul class="list-none m-0 p-0 [--depth:0]">
<li><a href="#tsl03-start" class="block my-px py-[7px] pe-2.5 ps-[calc(0.75rem+var(--depth)*0.875rem)] rounded-lg text-[13.5px] font-medium leading-tight text-tsl03-mut no-underline transition-colors duration-150 hover:text-tsl03-ink hover:bg-tsl03-hover focus-visible:outline-2 focus-visible:outline-tsl03-accent focus-visible:outline-offset-1">Getting started</a>
<ul class="list-none m-0 p-0 [--depth:1] border-s border-tsl03-line ms-[19px]">
<li><a href="#tsl03-install" class="block my-px py-[7px] pe-2.5 ps-3 rounded-lg text-[13.5px] font-medium leading-tight text-tsl03-mut no-underline transition-colors duration-150 hover:text-tsl03-ink hover:bg-tsl03-hover focus-visible:outline-2 focus-visible:outline-tsl03-accent focus-visible:outline-offset-1">Installation</a></li>
<li><a href="#tsl03-auth" class="block my-px py-[7px] pe-2.5 ps-3 rounded-lg text-[13.5px] font-medium leading-tight text-tsl03-mut no-underline transition-colors duration-150 hover:text-tsl03-ink hover:bg-tsl03-hover focus-visible:outline-2 focus-visible:outline-tsl03-accent focus-visible:outline-offset-1">Authentication</a>
<ul class="list-none m-0 p-0 [--depth:2] border-s border-tsl03-line ms-[15px]">
<li><a href="#tsl03-keys" class="block my-px py-[7px] pe-2.5 ps-3 rounded-lg text-[13.5px] font-medium leading-tight text-tsl03-mut no-underline transition-colors duration-150 hover:text-tsl03-ink hover:bg-tsl03-hover focus-visible:outline-2 focus-visible:outline-tsl03-accent focus-visible:outline-offset-1">API keys</a></li>
<li><a href="#tsl03-oauth" class="block my-px py-[7px] pe-2.5 ps-3 rounded-lg text-[13.5px] font-medium leading-tight text-tsl03-mut no-underline transition-colors duration-150 hover:text-tsl03-ink hover:bg-tsl03-hover focus-visible:outline-2 focus-visible:outline-tsl03-accent focus-visible:outline-offset-1">OAuth 2.0</a></li>
</ul>
</li>
</ul>
</li>
<li class="mt-1"><a href="#tsl03-core" class="block my-px py-[7px] pe-2.5 ps-[calc(0.75rem+var(--depth)*0.875rem)] rounded-lg text-[13.5px] font-medium leading-tight text-tsl03-mut no-underline transition-colors duration-150 hover:text-tsl03-ink hover:bg-tsl03-hover focus-visible:outline-2 focus-visible:outline-tsl03-accent focus-visible:outline-offset-1">Core concepts</a>
<ul class="list-none m-0 p-0 [--depth:1] border-s border-tsl03-line ms-[19px]">
<li><a href="#tsl03-webhooks" class="block my-px py-[7px] pe-2.5 ps-3 rounded-lg text-[13.5px] font-medium leading-tight text-tsl03-mut no-underline transition-colors duration-150 hover:text-tsl03-ink hover:bg-tsl03-hover focus-visible:outline-2 focus-visible:outline-tsl03-accent focus-visible:outline-offset-1">Webhooks</a></li>
<li><a href="#tsl03-errors" class="block my-px py-[7px] pe-2.5 ps-3 rounded-lg text-[13.5px] font-medium leading-tight text-tsl03-mut no-underline transition-colors duration-150 hover:text-tsl03-ink hover:bg-tsl03-hover focus-visible:outline-2 focus-visible:outline-tsl03-accent focus-visible:outline-offset-1">Error handling</a></li>
</ul>
</li>
</ul>
</nav>
</aside>
<main class="overflow-y-auto scroll-smooth px-11 py-9 grid gap-[34px] content-start motion-reduce:scroll-auto [scrollbar-gutter:stable]">
<section id="tsl03-start" class="scroll-mt-6"><h1 class="text-[26px] font-bold leading-tight tracking-tight">Getting started</h1><p class="mt-2 max-w-[58ch] text-sm leading-[1.65] text-tsl03-mut text-pretty">Click any sidebar item — the matching link lights via <code class="font-tsl03-mono text-[12.5px]">:target</code>, no scroll-spy script.</p></section>
<section id="tsl03-install" class="scroll-mt-6"><h2 class="text-xl font-bold leading-tight">Installation</h2><p class="mt-2 max-w-[58ch] text-sm leading-[1.65] text-tsl03-mut text-pretty">Install the SDK with your package manager of choice.</p></section>
<section id="tsl03-auth" class="scroll-mt-6"><h2 class="text-xl font-bold leading-tight">Authentication</h2><p class="mt-2 max-w-[58ch] text-sm leading-[1.65] text-tsl03-mut text-pretty">All requests are authenticated with a bearer token.</p></section>
<section id="tsl03-keys" class="scroll-mt-6"><h3 class="text-base font-semibold leading-snug">API keys</h3><p class="mt-2 max-w-[58ch] text-sm leading-[1.65] text-tsl03-mut text-pretty">Generate keys from the dashboard. Rotate them quarterly.</p></section>
<section id="tsl03-oauth" class="scroll-mt-6"><h3 class="text-base font-semibold leading-snug">OAuth 2.0</h3><p class="mt-2 max-w-[58ch] text-sm leading-[1.65] text-tsl03-mut text-pretty">Use the authorization-code flow for user-scoped access.</p></section>
<section id="tsl03-core" class="scroll-mt-6"><h2 class="text-xl font-bold leading-tight">Core concepts</h2><p class="mt-2 max-w-[58ch] text-sm leading-[1.65] text-tsl03-mut text-pretty">Resources, events and idempotency.</p></section>
<section id="tsl03-webhooks" class="scroll-mt-6"><h3 class="text-base font-semibold leading-snug">Webhooks</h3><p class="mt-2 max-w-[58ch] text-sm leading-[1.65] text-tsl03-mut text-pretty">Receive events at your endpoint within seconds.</p></section>
<section id="tsl03-errors" class="scroll-mt-6"><h3 class="text-base font-semibold leading-snug">Error handling</h3><p class="mt-2 max-w-[58ch] text-sm leading-[1.65] text-tsl03-mut text-pretty">Every error returns a typed code and a human message.</p></section>
</main>
</section>@import "tailwindcss";
@theme {
--color-tsl03-bg: #fdfdfc;
--color-tsl03-bg: oklch(0.99 0.002 100);
--color-tsl03-ink: #3b3f47;
--color-tsl03-ink: oklch(0.27 0.015 265);
--color-tsl03-mut: #767a84;
--color-tsl03-mut: oklch(0.52 0.012 265);
--color-tsl03-line: #e3e4e9;
--color-tsl03-line: oklch(0.9 0.007 265);
--color-tsl03-accent: #4a5fd0;
--color-tsl03-accent: oklch(0.55 0.17 265);
--color-tsl03-hover: rgb(59 63 71 / 0.05);
--color-tsl03-hover: color-mix(in oklab, oklch(0.27 0.015 265) 5%, transparent);
--color-tsl03-tint: rgb(74 95 208 / 0.1);
--color-tsl03-tint: color-mix(in oklab, oklch(0.55 0.17 265) 10%, transparent);
--font-tsl03-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
--font-tsl03-mono: 'JetBrains Mono', ui-monospace, monospace;
}
.tsl-03 {
width: 100%;
height: 100svh;
min-height: 100svh;
color-scheme: light;
}
/* STATE — the active link, keyed off the section the fragment points at. */
.tsl-03:has(#tsl03-start:target) a[href="#tsl03-start"],
.tsl-03:has(#tsl03-install:target) a[href="#tsl03-install"],
.tsl-03:has(#tsl03-auth:target) a[href="#tsl03-auth"],
.tsl-03:has(#tsl03-keys:target) a[href="#tsl03-keys"],
.tsl-03:has(#tsl03-oauth:target) a[href="#tsl03-oauth"],
.tsl-03:has(#tsl03-core:target) a[href="#tsl03-core"],
.tsl-03:has(#tsl03-webhooks:target) a[href="#tsl03-webhooks"],
.tsl-03:has(#tsl03-errors:target) a[href="#tsl03-errors"] {
color: var(--color-tsl03-accent);
background: var(--color-tsl03-tint);
font-weight: 600;
}@import "tailwindcss";
@theme {
--color-tsl03-bg: #fdfdfc;
--color-tsl03-bg: oklch(0.99 0.002 100);
--color-tsl03-ink: #3b3f47;
--color-tsl03-ink: oklch(0.27 0.015 265);
--color-tsl03-mut: #767a84;
--color-tsl03-mut: oklch(0.52 0.012 265);
--color-tsl03-line: #e3e4e9;
--color-tsl03-line: oklch(0.9 0.007 265);
--color-tsl03-accent: #4a5fd0;
--color-tsl03-accent: oklch(0.55 0.17 265);
--color-tsl03-hover: rgb(59 63 71 / 0.05);
--color-tsl03-hover: color-mix(in oklab, oklch(0.27 0.015 265) 5%, transparent);
--color-tsl03-tint: rgb(74 95 208 / 0.1);
--color-tsl03-tint: color-mix(in oklab, oklch(0.55 0.17 265) 10%, transparent);
--font-tsl03-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
--font-tsl03-mono: 'JetBrains Mono', ui-monospace, monospace;
}
.tsl-03 {
width: 100%;
height: 100svh;
min-height: 100svh;
color-scheme: light;
}
/* STATE — the active link, keyed off the section the fragment points at. */
.tsl-03:has(#tsl03-start:target) a[href="#tsl03-start"],
.tsl-03:has(#tsl03-install:target) a[href="#tsl03-install"],
.tsl-03:has(#tsl03-auth:target) a[href="#tsl03-auth"],
.tsl-03:has(#tsl03-keys:target) a[href="#tsl03-keys"],
.tsl-03:has(#tsl03-oauth:target) a[href="#tsl03-oauth"],
.tsl-03:has(#tsl03-core:target) a[href="#tsl03-core"],
.tsl-03:has(#tsl03-webhooks:target) a[href="#tsl03-webhooks"],
.tsl-03:has(#tsl03-errors:target) a[href="#tsl03-errors"] {
color: var(--color-tsl03-accent);
background: var(--color-tsl03-tint);
font-weight: 600;
}Setup — do this once per project
Skip this if your project already runs Tailwind v4.
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<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.
npm install tailwindcss @tailwindcss/vite
npm install tailwindcss @tailwindcss/vite// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})Any v4 setup works — Vite, PostCSS or the CLI. Nothing in this demo is bundler-specific.
@import "tailwindcss";
@theme {
--color-tsl03-bg: #fdfdfc;
--color-tsl03-bg: oklch(0.99 0.002 100);
--color-tsl03-ink: #3b3f47;
--color-tsl03-ink: oklch(0.27 0.015 265);
--color-tsl03-mut: #767a84;
--color-tsl03-mut: oklch(0.52 0.012 265);
--color-tsl03-line: #e3e4e9;
--color-tsl03-line: oklch(0.9 0.007 265);
--color-tsl03-accent: #4a5fd0;
--color-tsl03-accent: oklch(0.55 0.17 265);
--color-tsl03-hover: rgb(59 63 71 / 0.05);
--color-tsl03-hover: color-mix(in oklab, oklch(0.27 0.015 265) 5%, transparent);
--color-tsl03-tint: rgb(74 95 208 / 0.1);
--color-tsl03-tint: color-mix(in oklab, oklch(0.55 0.17 265) 10%, transparent);
--font-tsl03-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
--font-tsl03-mono: 'JetBrains Mono', ui-monospace, monospace;
}
.tsl-03 {
width: 100%;
height: 100svh;
min-height: 100svh;
color-scheme: light;
}
/* STATE — the active link, keyed off the section the fragment points at. */
.tsl-03:has(#tsl03-start:target) a[href="#tsl03-start"],
.tsl-03:has(#tsl03-install:target) a[href="#tsl03-install"],
.tsl-03:has(#tsl03-auth:target) a[href="#tsl03-auth"],
.tsl-03:has(#tsl03-keys:target) a[href="#tsl03-keys"],
.tsl-03:has(#tsl03-oauth:target) a[href="#tsl03-oauth"],
.tsl-03:has(#tsl03-core:target) a[href="#tsl03-core"],
.tsl-03:has(#tsl03-webhooks:target) a[href="#tsl03-webhooks"],
.tsl-03:has(#tsl03-errors:target) a[href="#tsl03-errors"] {
color: var(--color-tsl03-accent);
background: var(--color-tsl03-tint);
font-weight: 600;
}@import "tailwindcss";
@theme {
--color-tsl03-bg: #fdfdfc;
--color-tsl03-bg: oklch(0.99 0.002 100);
--color-tsl03-ink: #3b3f47;
--color-tsl03-ink: oklch(0.27 0.015 265);
--color-tsl03-mut: #767a84;
--color-tsl03-mut: oklch(0.52 0.012 265);
--color-tsl03-line: #e3e4e9;
--color-tsl03-line: oklch(0.9 0.007 265);
--color-tsl03-accent: #4a5fd0;
--color-tsl03-accent: oklch(0.55 0.17 265);
--color-tsl03-hover: rgb(59 63 71 / 0.05);
--color-tsl03-hover: color-mix(in oklab, oklch(0.27 0.015 265) 5%, transparent);
--color-tsl03-tint: rgb(74 95 208 / 0.1);
--color-tsl03-tint: color-mix(in oklab, oklch(0.55 0.17 265) 10%, transparent);
--font-tsl03-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
--font-tsl03-mono: 'JetBrains Mono', ui-monospace, monospace;
}
.tsl-03 {
width: 100%;
height: 100svh;
min-height: 100svh;
color-scheme: light;
}
/* STATE — the active link, keyed off the section the fragment points at. */
.tsl-03:has(#tsl03-start:target) a[href="#tsl03-start"],
.tsl-03:has(#tsl03-install:target) a[href="#tsl03-install"],
.tsl-03:has(#tsl03-auth:target) a[href="#tsl03-auth"],
.tsl-03:has(#tsl03-keys:target) a[href="#tsl03-keys"],
.tsl-03:has(#tsl03-oauth:target) a[href="#tsl03-oauth"],
.tsl-03:has(#tsl03-core:target) a[href="#tsl03-core"],
.tsl-03:has(#tsl03-webhooks:target) a[href="#tsl03-webhooks"],
.tsl-03:has(#tsl03-errors:target) a[href="#tsl03-errors"] {
color: var(--color-tsl03-accent);
background: var(--color-tsl03-tint);
font-weight: 600;
}<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">
<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.
- The comma-separated
:has(#id:target)chain — eight selectors in one rule in the CSS original — becomes one arbitrary variant per link. Longer in total characters, dramatically shorter to maintain: adding a docs section now touches one element instead of one shared rule far from the markup. --depthstays a raw custom property rather than becoming a@themeentry, because it is a per-instance counter, not a design-scale value.@themewould mint uselessdepth-*utilities.- UI change from the CSS original: nested rails now use logical
border-s/ms-*so the tree flips correctly in RTL, the content pane gainedscrollbar-gutter:stable, andmotion-reduce:scroll-autodisables smooth scrolling for users who asked for less motion — the CSS version guarded the transitions but not the scroll.
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 Documentation Sidebar with Multi-Level :target Tree
Source: https://codefronts.com/layouts/tailwind-css-sidebar-layouts/tailwind-documentation-sidebar-multi-level-target-tree/
The Stripe-style docs tree: three levels of nested navigation, a guide rail connecting each level, and the current section lit automatically as readers jump between anchors — no scroll-spy script. The port turns eight :has(#id:target) selectors into eight arbitrary variants that sit on the links they light.
## HTML
```html
<section class="tsl-03 w-full h-svh min-h-svh grid grid-cols-[250px_1fr] font-tsl03-sans bg-tsl03-bg text-tsl03-ink">
<aside class="border-r border-tsl03-line px-3 py-6 overflow-y-auto [scrollbar-gutter:stable]">
<nav aria-label="Documentation">
<ul class="list-none m-0 p-0 [--depth:0]">
<li><a href="#tsl03-start" class="block my-px py-[7px] pe-2.5 ps-[calc(0.75rem+var(--depth)*0.875rem)] rounded-lg text-[13.5px] font-medium leading-tight text-tsl03-mut no-underline transition-colors duration-150 hover:text-tsl03-ink hover:bg-tsl03-hover focus-visible:outline-2 focus-visible:outline-tsl03-accent focus-visible:outline-offset-1">Getting started</a>
<ul class="list-none m-0 p-0 [--depth:1] border-s border-tsl03-line ms-[19px]">
<li><a href="#tsl03-install" class="block my-px py-[7px] pe-2.5 ps-3 rounded-lg text-[13.5px] font-medium leading-tight text-tsl03-mut no-underline transition-colors duration-150 hover:text-tsl03-ink hover:bg-tsl03-hover focus-visible:outline-2 focus-visible:outline-tsl03-accent focus-visible:outline-offset-1">Installation</a></li>
<li><a href="#tsl03-auth" class="block my-px py-[7px] pe-2.5 ps-3 rounded-lg text-[13.5px] font-medium leading-tight text-tsl03-mut no-underline transition-colors duration-150 hover:text-tsl03-ink hover:bg-tsl03-hover focus-visible:outline-2 focus-visible:outline-tsl03-accent focus-visible:outline-offset-1">Authentication</a>
<ul class="list-none m-0 p-0 [--depth:2] border-s border-tsl03-line ms-[15px]">
<li><a href="#tsl03-keys" class="block my-px py-[7px] pe-2.5 ps-3 rounded-lg text-[13.5px] font-medium leading-tight text-tsl03-mut no-underline transition-colors duration-150 hover:text-tsl03-ink hover:bg-tsl03-hover focus-visible:outline-2 focus-visible:outline-tsl03-accent focus-visible:outline-offset-1">API keys</a></li>
<li><a href="#tsl03-oauth" class="block my-px py-[7px] pe-2.5 ps-3 rounded-lg text-[13.5px] font-medium leading-tight text-tsl03-mut no-underline transition-colors duration-150 hover:text-tsl03-ink hover:bg-tsl03-hover focus-visible:outline-2 focus-visible:outline-tsl03-accent focus-visible:outline-offset-1">OAuth 2.0</a></li>
</ul>
</li>
</ul>
</li>
<li class="mt-1"><a href="#tsl03-core" class="block my-px py-[7px] pe-2.5 ps-[calc(0.75rem+var(--depth)*0.875rem)] rounded-lg text-[13.5px] font-medium leading-tight text-tsl03-mut no-underline transition-colors duration-150 hover:text-tsl03-ink hover:bg-tsl03-hover focus-visible:outline-2 focus-visible:outline-tsl03-accent focus-visible:outline-offset-1">Core concepts</a>
<ul class="list-none m-0 p-0 [--depth:1] border-s border-tsl03-line ms-[19px]">
<li><a href="#tsl03-webhooks" class="block my-px py-[7px] pe-2.5 ps-3 rounded-lg text-[13.5px] font-medium leading-tight text-tsl03-mut no-underline transition-colors duration-150 hover:text-tsl03-ink hover:bg-tsl03-hover focus-visible:outline-2 focus-visible:outline-tsl03-accent focus-visible:outline-offset-1">Webhooks</a></li>
<li><a href="#tsl03-errors" class="block my-px py-[7px] pe-2.5 ps-3 rounded-lg text-[13.5px] font-medium leading-tight text-tsl03-mut no-underline transition-colors duration-150 hover:text-tsl03-ink hover:bg-tsl03-hover focus-visible:outline-2 focus-visible:outline-tsl03-accent focus-visible:outline-offset-1">Error handling</a></li>
</ul>
</li>
</ul>
</nav>
</aside>
<main class="overflow-y-auto scroll-smooth px-11 py-9 grid gap-[34px] content-start motion-reduce:scroll-auto [scrollbar-gutter:stable]">
<section id="tsl03-start" class="scroll-mt-6"><h1 class="text-[26px] font-bold leading-tight tracking-tight">Getting started</h1><p class="mt-2 max-w-[58ch] text-sm leading-[1.65] text-tsl03-mut text-pretty">Click any sidebar item — the matching link lights via <code class="font-tsl03-mono text-[12.5px]">:target</code>, no scroll-spy script.</p></section>
<section id="tsl03-install" class="scroll-mt-6"><h2 class="text-xl font-bold leading-tight">Installation</h2><p class="mt-2 max-w-[58ch] text-sm leading-[1.65] text-tsl03-mut text-pretty">Install the SDK with your package manager of choice.</p></section>
<section id="tsl03-auth" class="scroll-mt-6"><h2 class="text-xl font-bold leading-tight">Authentication</h2><p class="mt-2 max-w-[58ch] text-sm leading-[1.65] text-tsl03-mut text-pretty">All requests are authenticated with a bearer token.</p></section>
<section id="tsl03-keys" class="scroll-mt-6"><h3 class="text-base font-semibold leading-snug">API keys</h3><p class="mt-2 max-w-[58ch] text-sm leading-[1.65] text-tsl03-mut text-pretty">Generate keys from the dashboard. Rotate them quarterly.</p></section>
<section id="tsl03-oauth" class="scroll-mt-6"><h3 class="text-base font-semibold leading-snug">OAuth 2.0</h3><p class="mt-2 max-w-[58ch] text-sm leading-[1.65] text-tsl03-mut text-pretty">Use the authorization-code flow for user-scoped access.</p></section>
<section id="tsl03-core" class="scroll-mt-6"><h2 class="text-xl font-bold leading-tight">Core concepts</h2><p class="mt-2 max-w-[58ch] text-sm leading-[1.65] text-tsl03-mut text-pretty">Resources, events and idempotency.</p></section>
<section id="tsl03-webhooks" class="scroll-mt-6"><h3 class="text-base font-semibold leading-snug">Webhooks</h3><p class="mt-2 max-w-[58ch] text-sm leading-[1.65] text-tsl03-mut text-pretty">Receive events at your endpoint within seconds.</p></section>
<section id="tsl03-errors" class="scroll-mt-6"><h3 class="text-base font-semibold leading-snug">Error handling</h3><p class="mt-2 max-w-[58ch] text-sm leading-[1.65] text-tsl03-mut text-pretty">Every error returns a typed code and a human message.</p></section>
</main>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tsl03-bg: #fdfdfc;
--color-tsl03-bg: oklch(0.99 0.002 100);
--color-tsl03-ink: #3b3f47;
--color-tsl03-ink: oklch(0.27 0.015 265);
--color-tsl03-mut: #767a84;
--color-tsl03-mut: oklch(0.52 0.012 265);
--color-tsl03-line: #e3e4e9;
--color-tsl03-line: oklch(0.9 0.007 265);
--color-tsl03-accent: #4a5fd0;
--color-tsl03-accent: oklch(0.55 0.17 265);
--color-tsl03-hover: rgb(59 63 71 / 0.05);
--color-tsl03-hover: color-mix(in oklab, oklch(0.27 0.015 265) 5%, transparent);
--color-tsl03-tint: rgb(74 95 208 / 0.1);
--color-tsl03-tint: color-mix(in oklab, oklch(0.55 0.17 265) 10%, transparent);
--font-tsl03-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
--font-tsl03-mono: 'JetBrains Mono', ui-monospace, monospace;
}
.tsl-03 {
width: 100%;
height: 100svh;
min-height: 100svh;
color-scheme: light;
}
/* STATE — the active link, keyed off the section the fragment points at. */
.tsl-03:has(#tsl03-start:target) a[href="#tsl03-start"],
.tsl-03:has(#tsl03-install:target) a[href="#tsl03-install"],
.tsl-03:has(#tsl03-auth:target) a[href="#tsl03-auth"],
.tsl-03:has(#tsl03-keys:target) a[href="#tsl03-keys"],
.tsl-03:has(#tsl03-oauth:target) a[href="#tsl03-oauth"],
.tsl-03:has(#tsl03-core:target) a[href="#tsl03-core"],
.tsl-03:has(#tsl03-webhooks:target) a[href="#tsl03-webhooks"],
.tsl-03:has(#tsl03-errors:target) a[href="#tsl03-errors"] {
color: var(--color-tsl03-accent);
background: var(--color-tsl03-tint);
font-weight: 600;
}
```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 Documentation Sidebar with Multi-Level :target Tree
Source: https://codefronts.com/layouts/tailwind-css-sidebar-layouts/tailwind-documentation-sidebar-multi-level-target-tree/
The Stripe-style docs tree: three levels of nested navigation, a guide rail connecting each level, and the current section lit automatically as readers jump between anchors — no scroll-spy script. The port turns eight :has(#id:target) selectors into eight arbitrary variants that sit on the links they light.
## HTML
```html
<section class="tsl-03 w-full h-svh min-h-svh grid grid-cols-[250px_1fr] font-tsl03-sans bg-tsl03-bg text-tsl03-ink">
<aside class="border-r border-tsl03-line px-3 py-6 overflow-y-auto [scrollbar-gutter:stable]">
<nav aria-label="Documentation">
<ul class="list-none m-0 p-0 [--depth:0]">
<li><a href="#tsl03-start" class="block my-px py-[7px] pe-2.5 ps-[calc(0.75rem+var(--depth)*0.875rem)] rounded-lg text-[13.5px] font-medium leading-tight text-tsl03-mut no-underline transition-colors duration-150 hover:text-tsl03-ink hover:bg-tsl03-hover focus-visible:outline-2 focus-visible:outline-tsl03-accent focus-visible:outline-offset-1">Getting started</a>
<ul class="list-none m-0 p-0 [--depth:1] border-s border-tsl03-line ms-[19px]">
<li><a href="#tsl03-install" class="block my-px py-[7px] pe-2.5 ps-3 rounded-lg text-[13.5px] font-medium leading-tight text-tsl03-mut no-underline transition-colors duration-150 hover:text-tsl03-ink hover:bg-tsl03-hover focus-visible:outline-2 focus-visible:outline-tsl03-accent focus-visible:outline-offset-1">Installation</a></li>
<li><a href="#tsl03-auth" class="block my-px py-[7px] pe-2.5 ps-3 rounded-lg text-[13.5px] font-medium leading-tight text-tsl03-mut no-underline transition-colors duration-150 hover:text-tsl03-ink hover:bg-tsl03-hover focus-visible:outline-2 focus-visible:outline-tsl03-accent focus-visible:outline-offset-1">Authentication</a>
<ul class="list-none m-0 p-0 [--depth:2] border-s border-tsl03-line ms-[15px]">
<li><a href="#tsl03-keys" class="block my-px py-[7px] pe-2.5 ps-3 rounded-lg text-[13.5px] font-medium leading-tight text-tsl03-mut no-underline transition-colors duration-150 hover:text-tsl03-ink hover:bg-tsl03-hover focus-visible:outline-2 focus-visible:outline-tsl03-accent focus-visible:outline-offset-1">API keys</a></li>
<li><a href="#tsl03-oauth" class="block my-px py-[7px] pe-2.5 ps-3 rounded-lg text-[13.5px] font-medium leading-tight text-tsl03-mut no-underline transition-colors duration-150 hover:text-tsl03-ink hover:bg-tsl03-hover focus-visible:outline-2 focus-visible:outline-tsl03-accent focus-visible:outline-offset-1">OAuth 2.0</a></li>
</ul>
</li>
</ul>
</li>
<li class="mt-1"><a href="#tsl03-core" class="block my-px py-[7px] pe-2.5 ps-[calc(0.75rem+var(--depth)*0.875rem)] rounded-lg text-[13.5px] font-medium leading-tight text-tsl03-mut no-underline transition-colors duration-150 hover:text-tsl03-ink hover:bg-tsl03-hover focus-visible:outline-2 focus-visible:outline-tsl03-accent focus-visible:outline-offset-1">Core concepts</a>
<ul class="list-none m-0 p-0 [--depth:1] border-s border-tsl03-line ms-[19px]">
<li><a href="#tsl03-webhooks" class="block my-px py-[7px] pe-2.5 ps-3 rounded-lg text-[13.5px] font-medium leading-tight text-tsl03-mut no-underline transition-colors duration-150 hover:text-tsl03-ink hover:bg-tsl03-hover focus-visible:outline-2 focus-visible:outline-tsl03-accent focus-visible:outline-offset-1">Webhooks</a></li>
<li><a href="#tsl03-errors" class="block my-px py-[7px] pe-2.5 ps-3 rounded-lg text-[13.5px] font-medium leading-tight text-tsl03-mut no-underline transition-colors duration-150 hover:text-tsl03-ink hover:bg-tsl03-hover focus-visible:outline-2 focus-visible:outline-tsl03-accent focus-visible:outline-offset-1">Error handling</a></li>
</ul>
</li>
</ul>
</nav>
</aside>
<main class="overflow-y-auto scroll-smooth px-11 py-9 grid gap-[34px] content-start motion-reduce:scroll-auto [scrollbar-gutter:stable]">
<section id="tsl03-start" class="scroll-mt-6"><h1 class="text-[26px] font-bold leading-tight tracking-tight">Getting started</h1><p class="mt-2 max-w-[58ch] text-sm leading-[1.65] text-tsl03-mut text-pretty">Click any sidebar item — the matching link lights via <code class="font-tsl03-mono text-[12.5px]">:target</code>, no scroll-spy script.</p></section>
<section id="tsl03-install" class="scroll-mt-6"><h2 class="text-xl font-bold leading-tight">Installation</h2><p class="mt-2 max-w-[58ch] text-sm leading-[1.65] text-tsl03-mut text-pretty">Install the SDK with your package manager of choice.</p></section>
<section id="tsl03-auth" class="scroll-mt-6"><h2 class="text-xl font-bold leading-tight">Authentication</h2><p class="mt-2 max-w-[58ch] text-sm leading-[1.65] text-tsl03-mut text-pretty">All requests are authenticated with a bearer token.</p></section>
<section id="tsl03-keys" class="scroll-mt-6"><h3 class="text-base font-semibold leading-snug">API keys</h3><p class="mt-2 max-w-[58ch] text-sm leading-[1.65] text-tsl03-mut text-pretty">Generate keys from the dashboard. Rotate them quarterly.</p></section>
<section id="tsl03-oauth" class="scroll-mt-6"><h3 class="text-base font-semibold leading-snug">OAuth 2.0</h3><p class="mt-2 max-w-[58ch] text-sm leading-[1.65] text-tsl03-mut text-pretty">Use the authorization-code flow for user-scoped access.</p></section>
<section id="tsl03-core" class="scroll-mt-6"><h2 class="text-xl font-bold leading-tight">Core concepts</h2><p class="mt-2 max-w-[58ch] text-sm leading-[1.65] text-tsl03-mut text-pretty">Resources, events and idempotency.</p></section>
<section id="tsl03-webhooks" class="scroll-mt-6"><h3 class="text-base font-semibold leading-snug">Webhooks</h3><p class="mt-2 max-w-[58ch] text-sm leading-[1.65] text-tsl03-mut text-pretty">Receive events at your endpoint within seconds.</p></section>
<section id="tsl03-errors" class="scroll-mt-6"><h3 class="text-base font-semibold leading-snug">Error handling</h3><p class="mt-2 max-w-[58ch] text-sm leading-[1.65] text-tsl03-mut text-pretty">Every error returns a typed code and a human message.</p></section>
</main>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tsl03-bg: #fdfdfc;
--color-tsl03-bg: oklch(0.99 0.002 100);
--color-tsl03-ink: #3b3f47;
--color-tsl03-ink: oklch(0.27 0.015 265);
--color-tsl03-mut: #767a84;
--color-tsl03-mut: oklch(0.52 0.012 265);
--color-tsl03-line: #e3e4e9;
--color-tsl03-line: oklch(0.9 0.007 265);
--color-tsl03-accent: #4a5fd0;
--color-tsl03-accent: oklch(0.55 0.17 265);
--color-tsl03-hover: rgb(59 63 71 / 0.05);
--color-tsl03-hover: color-mix(in oklab, oklch(0.27 0.015 265) 5%, transparent);
--color-tsl03-tint: rgb(74 95 208 / 0.1);
--color-tsl03-tint: color-mix(in oklab, oklch(0.55 0.17 265) 10%, transparent);
--font-tsl03-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
--font-tsl03-mono: 'JetBrains Mono', ui-monospace, monospace;
}
.tsl-03 {
width: 100%;
height: 100svh;
min-height: 100svh;
color-scheme: light;
}
/* STATE — the active link, keyed off the section the fragment points at. */
.tsl-03:has(#tsl03-start:target) a[href="#tsl03-start"],
.tsl-03:has(#tsl03-install:target) a[href="#tsl03-install"],
.tsl-03:has(#tsl03-auth:target) a[href="#tsl03-auth"],
.tsl-03:has(#tsl03-keys:target) a[href="#tsl03-keys"],
.tsl-03:has(#tsl03-oauth:target) a[href="#tsl03-oauth"],
.tsl-03:has(#tsl03-core:target) a[href="#tsl03-core"],
.tsl-03:has(#tsl03-webhooks:target) a[href="#tsl03-webhooks"],
.tsl-03:has(#tsl03-errors:target) a[href="#tsl03-errors"] {
color: var(--color-tsl03-accent);
background: var(--color-tsl03-tint);
font-weight: 600;
}
```How this works
Indentation stays a variable, not a scale. Tailwind's spacing scale is the wrong tool for depth-derived padding: you would need a class per level. The CSS answer ports directly as an arbitrary value — [--depth:1] on the list, ps-[calc(0.75rem+var(--depth)*0.875rem)] on the link — and a fourth level costs one [--depth:3], not a new rule.
The active link is a relational variant. The CSS version wrote a comma-separated chain of .sbl-04:has(#x:target) a[href="#x"]. Tailwind inverts the direction: each link carries [:has(#tsl03-auth:target)_&]:bg-tsl03-tint [:has(#tsl03-auth:target)_&]:font-semibold, so the rule that lights a link is written on that link. The chain disappears; adding a section adds classes to exactly one element.
It is still a declarative scroll-spy for click navigation. Fragment changes, the section becomes :target, the matching link lights. No listener, no IntersectionObserver, no hydration cost.
Make it yours
- Deeper trees:
[--depth:3]on the next<ul>— the padding calc already handles it. - Server-rendered docs should use
aria-current="page"and thearia-[current=page]:variant instead;:targetis for single-page anchor navigation. - Rail width:
w-[250px]suits two levels; gow-[280px]for API references with long method names.
Gotchas — read before shipping
scroll-smoothmust sit on the element that scrolls (the content pane), not the sidebar or the root.- Ids are document-global: prefix them (
tsl03-) or a second demo on the page will steal the target. - The arbitrary variant
[:has(#id:target)_&]:needs the underscore for the descendant combinator — a literal space breaks the class name. scroll-mt-*on target headings is mandatory under a sticky header, or anchors land underneath it.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
:has() gates the active-link highlight (Firefox 121); the depth-variable indentation works in any engine with custom properties.