30 Tailwind CSS Sidebar Layouts
03 / 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 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.

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-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 &mdash; 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;
}

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-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;
}
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.

  • 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.
  • --depth stays a raw custom property rather than becoming a @theme entry, because it is a per-instance counter, not a design-scale value. @theme would mint useless depth-* 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 gained scrollbar-gutter:stable, and motion-reduce:scroll-auto disables smooth scrolling for users who asked for less motion — the CSS version guarded the transitions but not the scroll.
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 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 &mdash; 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 &mdash; 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 the aria-[current=page]: variant instead; :target is for single-page anchor navigation.
  • Rail width: w-[250px] suits two levels; go w-[280px] for API references with long method names.

Gotchas — read before shipping

  • scroll-smooth must 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

ChromeSafariFirefoxEdge
111+16.4+128+111+

:has() gates the active-link highlight (Firefox 121); the depth-variable indentation works in any engine with custom properties.

Techniques used in this demo

Utilitiesfirst-class utility or variant

Theme tokensset in @theme, used by a utility

Search CodeFronts

Loading…