15 / 20
All 20 in 20 Tailwind CSS Sidebars
- 01Tailwind Accessible Sidebar Navigation ARIA Reference
- 02Tailwind Sidebar Focus Trap and Restore on Close
- 03Tailwind Inert Background Content Behind a Drawer
- 04Tailwind Sidebar Navigation Is Not a Tab List
- 05Tailwind Announcing Collapsed and Expanded Sidebar State
- 06Tailwind Roving Tabindex for Sidebar Keyboard Navigation
- 07Tailwind Persisting Sidebar Collapse State Across Reloads
- 08Tailwind Sidebar Scroll Containment with Overscroll Behavior
- 09Tailwind Nested Submenu Keyboard and Screen Reader Behavior
- 10Tailwind Resizable Sidebar with a Keyboard Accessible Handle
- 11Pure Tailwind Collapsible Sidebar with Checkbox State
- 12Tailwind Responsive Sidebar Breakpoint Behavior
- 13Pure Tailwind Icon Rail Sidebar with Accessible Labels
- 14Tailwind Dashboard Sidebar with Current Page State
- 15Pure Tailwind Multi Level Sidebar Menu Disclosure Pattern
- 16Pure Tailwind Accordion Sidebar with Details and Summary
- 17Pure Tailwind Sticky Full Height Sidebar Layout Mechanics
- 18Pure Tailwind Floating Sidebar Stacking and Overlay Order
- 19Tailwind Off Canvas Sidebar Drawer with Focus Return
- 20Pure Tailwind Sidebar Footer Pinning with Scrollable Nav
Pure Tailwind Multi Level Sidebar Menu Disclosure Pattern
Three levels deep, and the demo exists to document the one Tailwind trap that bites here: group-open: matches through EVERY open ancestor, so a nested tree rotates chevrons that are still closed. The fix is named groups — group/l1 and group/l2 with group-open/l1: and group-open/l2: — and once they are named, three levels cost no more than one.
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="tsb-15 block relative w-full min-h-svh bg-tsb15-bg text-tsb15-ink font-tsb15-sans leading-[1.55]" aria-label="Basalt knowledge base">
<div class="absolute top-3.5 right-3.5 z-60">
<input class="tsb-15-theme-input peer absolute inset-0 w-full h-full m-0 opacity-0 cursor-pointer" type="checkbox" id="tsb-15-theme">
<label class="grid place-items-center w-11 h-11 border border-tsb15-line bg-tsb15-surf text-tsb15-ink cursor-pointer peer-focus-visible:outline-2 peer-focus-visible:outline-tsb15-acc peer-focus-visible:outline-offset-2" for="tsb-15-theme">
<svg class="w-4.5 h-4.5" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="8.5" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M12 3.5a8.5 8.5 0 0 0 0 17Z" fill="currentColor"/></svg>
<span class="absolute w-px h-px overflow-hidden whitespace-nowrap [clip-path:inset(50%)]">Toggle dark theme</span>
</label>
</div>
<div class="grid grid-cols-[minmax(0,1fr)] min-h-svh bg-tsb15-surf r48:grid-cols-[minmax(0,17.5rem)_minmax(0,1fr)]">
<nav class="min-w-0 px-2.5 pt-4 pb-5 border-b border-tsb15-line r48:border-b-0 r48:border-r r48:border-tsb15-line" aria-label="Basalt knowledge base">
<p class="m-0 mb-3 px-2 font-tsb15-display text-[1.05rem] tracking-[0.02em]">Handbook</p>
<ul class="list-none m-0 p-0">
<li><a class="flex items-center min-w-0 min-h-11 px-2.5 py-2 text-[0.91rem] text-tsb15-ink no-underline hover:bg-tsb15-surf2 focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2" href="#tsb-15-main">Start here</a></li>
</ul>
<details class="group/l1" open>
<summary class="flex items-center gap-2 min-h-11 px-2.5 py-2 text-[0.91rem] font-semibold cursor-pointer list-none [&::-webkit-details-marker]:hidden before:content-[''] before:flex-none before:w-1.5 before:h-1.5 before:border-r-[1.5px] before:border-b-[1.5px] before:border-tsb15-mut before:-rotate-45 before:transition-transform before:duration-150 group-open/l1:before:rotate-45 group-has-[a[aria-current=page]]/l1:text-tsb15-acc hover:bg-tsb15-surf2 focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2 motion-reduce:before:transition-none">Operations</summary>
<div class="ml-2.5 border-l border-tsb15-line">
<details class="group/l2" open>
<summary class="flex items-center gap-2 min-h-11 py-2 pr-2.5 pl-4.5 text-[0.89rem] font-medium cursor-pointer list-none [&::-webkit-details-marker]:hidden before:content-[''] before:flex-none before:w-1.5 before:h-1.5 before:border-r-[1.5px] before:border-b-[1.5px] before:border-tsb15-mut before:-rotate-45 before:transition-transform before:duration-150 group-open/l2:before:rotate-45 group-has-[a[aria-current=page]]/l2:text-tsb15-acc hover:bg-tsb15-surf2 focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2 motion-reduce:before:transition-none">Deployments</summary>
<ul class="list-none m-0 p-0">
<li><a class="flex items-center min-w-0 min-h-11 py-2 pr-2.5 pl-7.5 text-[0.88rem] text-tsb15-mut no-underline hover:bg-tsb15-surf2 focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2 aria-[current=page]:bg-tsb15-accsoft aria-[current=page]:text-tsb15-acc aria-[current=page]:font-semibold" href="#tsb-15-main">Promotion policy</a></li>
<li><a class="flex items-center min-w-0 min-h-11 py-2 pr-2.5 pl-7.5 text-[0.88rem] text-tsb15-mut no-underline hover:bg-tsb15-surf2 focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2 aria-[current=page]:bg-tsb15-accsoft aria-[current=page]:text-tsb15-acc aria-[current=page]:font-semibold" href="#tsb-15-main" aria-current="page">Rollback runbook</a></li>
<li><a class="flex items-center min-w-0 min-h-11 py-2 pr-2.5 pl-7.5 text-[0.88rem] text-tsb15-mut no-underline hover:bg-tsb15-surf2 focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2 aria-[current=page]:bg-tsb15-accsoft aria-[current=page]:text-tsb15-acc aria-[current=page]:font-semibold" href="#tsb-15-main">Region failover</a></li>
</ul>
</details>
<details class="group/l2">
<summary class="flex items-center gap-2 min-h-11 py-2 pr-2.5 pl-4.5 text-[0.89rem] font-medium cursor-pointer list-none [&::-webkit-details-marker]:hidden before:content-[''] before:flex-none before:w-1.5 before:h-1.5 before:border-r-[1.5px] before:border-b-[1.5px] before:border-tsb15-mut before:-rotate-45 before:transition-transform before:duration-150 group-open/l2:before:rotate-45 group-has-[a[aria-current=page]]/l2:text-tsb15-acc hover:bg-tsb15-surf2 focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2 motion-reduce:before:transition-none">On call</summary>
<ul class="list-none m-0 p-0">
<li><a class="flex items-center min-w-0 min-h-11 py-2 pr-2.5 pl-7.5 text-[0.88rem] text-tsb15-mut no-underline hover:bg-tsb15-surf2 focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2" href="#tsb-15-main">Escalation ladder</a></li>
<li><a class="flex items-center min-w-0 min-h-11 py-2 pr-2.5 pl-7.5 text-[0.88rem] text-tsb15-mut no-underline hover:bg-tsb15-surf2 focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2" href="#tsb-15-main">Incident review</a></li>
</ul>
</details>
</div>
</details>
<details class="group/l1">
<summary class="flex items-center gap-2 min-h-11 px-2.5 py-2 text-[0.91rem] font-semibold cursor-pointer list-none [&::-webkit-details-marker]:hidden before:content-[''] before:flex-none before:w-1.5 before:h-1.5 before:border-r-[1.5px] before:border-b-[1.5px] before:border-tsb15-mut before:-rotate-45 before:transition-transform before:duration-150 group-open/l1:before:rotate-45 group-has-[a[aria-current=page]]/l1:text-tsb15-acc hover:bg-tsb15-surf2 focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2 motion-reduce:before:transition-none">People</summary>
<div class="ml-2.5 border-l border-tsb15-line">
<details class="group/l2">
<summary class="flex items-center gap-2 min-h-11 py-2 pr-2.5 pl-4.5 text-[0.89rem] font-medium cursor-pointer list-none [&::-webkit-details-marker]:hidden before:content-[''] before:flex-none before:w-1.5 before:h-1.5 before:border-r-[1.5px] before:border-b-[1.5px] before:border-tsb15-mut before:-rotate-45 before:transition-transform before:duration-150 group-open/l2:before:rotate-45 group-has-[a[aria-current=page]]/l2:text-tsb15-acc hover:bg-tsb15-surf2 focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2 motion-reduce:before:transition-none">Onboarding</summary>
<ul class="list-none m-0 p-0">
<li><a class="flex items-center min-w-0 min-h-11 py-2 pr-2.5 pl-7.5 text-[0.88rem] text-tsb15-mut no-underline hover:bg-tsb15-surf2 focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2" href="#tsb-15-main">First week</a></li>
<li><a class="flex items-center min-w-0 min-h-11 py-2 pr-2.5 pl-7.5 text-[0.88rem] text-tsb15-mut no-underline hover:bg-tsb15-surf2 focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2" href="#tsb-15-main">Access requests</a></li>
</ul>
</details>
<details class="group/l2">
<summary class="flex items-center gap-2 min-h-11 py-2 pr-2.5 pl-4.5 text-[0.89rem] font-medium cursor-pointer list-none [&::-webkit-details-marker]:hidden before:content-[''] before:flex-none before:w-1.5 before:h-1.5 before:border-r-[1.5px] before:border-b-[1.5px] before:border-tsb15-mut before:-rotate-45 before:transition-transform before:duration-150 group-open/l2:before:rotate-45 group-has-[a[aria-current=page]]/l2:text-tsb15-acc hover:bg-tsb15-surf2 focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2 motion-reduce:before:transition-none">Reviews</summary>
<ul class="list-none m-0 p-0">
<li><a class="flex items-center min-w-0 min-h-11 py-2 pr-2.5 pl-7.5 text-[0.88rem] text-tsb15-mut no-underline hover:bg-tsb15-surf2 focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2" href="#tsb-15-main">Cycle calendar</a></li>
<li><a class="flex items-center min-w-0 min-h-11 py-2 pr-2.5 pl-7.5 text-[0.88rem] text-tsb15-mut no-underline hover:bg-tsb15-surf2 focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2" href="#tsb-15-main">Writing feedback</a></li>
</ul>
</details>
</div>
</details>
</nav>
<main class="min-w-0 content-start px-5 pt-6 pb-10 r48:px-9 r48:pt-10 r48:pb-12" id="tsb-15-main">
<p class="m-0 mb-1 text-[0.82rem] text-tsb15-mut">Operations / Deployments</p>
<h2 class="m-0 mb-4.5 font-tsb15-display text-[clamp(1.45rem,3.6vw,2rem)] leading-[1.1]">Rollback runbook</h2>
<nav class="mb-5 px-3.5 py-3 max-w-[44rem] bg-tsb15-surf2" aria-label="On this page">
<p class="m-0 mb-2 text-[0.7rem] tracking-[0.1em] uppercase text-tsb15-mut">On this page</p>
<ul class="list-none m-0 p-0 flex flex-wrap gap-1.5">
<li><a class="inline-flex items-center min-h-11 px-3.5 bg-tsb15-surf border border-tsb15-line text-[0.87rem] text-tsb15-ink no-underline hover:bg-tsb15-bg focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2 aria-[current=page]:bg-tsb15-acc aria-[current=page]:border-tsb15-acc aria-[current=page]:text-tsb15-onacc aria-[current=page]:font-semibold" href="#tsb-15-main" aria-current="page">Before you start</a></li>
<li><a class="inline-flex items-center min-h-11 px-3.5 bg-tsb15-surf border border-tsb15-line text-[0.87rem] text-tsb15-ink no-underline hover:bg-tsb15-bg focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2" href="#tsb-15-main">Pointer move</a></li>
<li><a class="inline-flex items-center min-h-11 px-3.5 bg-tsb15-surf border border-tsb15-line text-[0.87rem] text-tsb15-ink no-underline hover:bg-tsb15-bg focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2" href="#tsb-15-main">Verifying</a></li>
<li><a class="inline-flex items-center min-h-11 px-3.5 bg-tsb15-surf border border-tsb15-line text-[0.87rem] text-tsb15-ink no-underline hover:bg-tsb15-bg focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2" href="#tsb-15-main">Post-mortem</a></li>
</ul>
</nav>
<p class="m-0 mb-3 max-w-[40rem] text-[0.94rem] text-tsb15-mut text-pretty">Promotion is a pointer move, so a rollback is the same operation in reverse and completes in under a second. The slow part is verification — confirm the health checks on the previous build before you announce it.</p>
<p class="m-0 mb-3 max-w-[40rem] text-[0.94rem] text-tsb15-mut text-pretty">Announce in the incident channel first, then update the status page. If the previous build is more than seven days old, check the migration log before promoting it.</p>
</main>
</div>
</section><section class="tsb-15 block relative w-full min-h-svh bg-tsb15-bg text-tsb15-ink font-tsb15-sans leading-[1.55]" aria-label="Basalt knowledge base">
<div class="absolute top-3.5 right-3.5 z-60">
<input class="tsb-15-theme-input peer absolute inset-0 w-full h-full m-0 opacity-0 cursor-pointer" type="checkbox" id="tsb-15-theme">
<label class="grid place-items-center w-11 h-11 border border-tsb15-line bg-tsb15-surf text-tsb15-ink cursor-pointer peer-focus-visible:outline-2 peer-focus-visible:outline-tsb15-acc peer-focus-visible:outline-offset-2" for="tsb-15-theme">
<svg class="w-4.5 h-4.5" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="8.5" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M12 3.5a8.5 8.5 0 0 0 0 17Z" fill="currentColor"/></svg>
<span class="absolute w-px h-px overflow-hidden whitespace-nowrap [clip-path:inset(50%)]">Toggle dark theme</span>
</label>
</div>
<div class="grid grid-cols-[minmax(0,1fr)] min-h-svh bg-tsb15-surf r48:grid-cols-[minmax(0,17.5rem)_minmax(0,1fr)]">
<nav class="min-w-0 px-2.5 pt-4 pb-5 border-b border-tsb15-line r48:border-b-0 r48:border-r r48:border-tsb15-line" aria-label="Basalt knowledge base">
<p class="m-0 mb-3 px-2 font-tsb15-display text-[1.05rem] tracking-[0.02em]">Handbook</p>
<ul class="list-none m-0 p-0">
<li><a class="flex items-center min-w-0 min-h-11 px-2.5 py-2 text-[0.91rem] text-tsb15-ink no-underline hover:bg-tsb15-surf2 focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2" href="#tsb-15-main">Start here</a></li>
</ul>
<details class="group/l1" open>
<summary class="flex items-center gap-2 min-h-11 px-2.5 py-2 text-[0.91rem] font-semibold cursor-pointer list-none [&::-webkit-details-marker]:hidden before:content-[''] before:flex-none before:w-1.5 before:h-1.5 before:border-r-[1.5px] before:border-b-[1.5px] before:border-tsb15-mut before:-rotate-45 before:transition-transform before:duration-150 group-open/l1:before:rotate-45 group-has-[a[aria-current=page]]/l1:text-tsb15-acc hover:bg-tsb15-surf2 focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2 motion-reduce:before:transition-none">Operations</summary>
<div class="ml-2.5 border-l border-tsb15-line">
<details class="group/l2" open>
<summary class="flex items-center gap-2 min-h-11 py-2 pr-2.5 pl-4.5 text-[0.89rem] font-medium cursor-pointer list-none [&::-webkit-details-marker]:hidden before:content-[''] before:flex-none before:w-1.5 before:h-1.5 before:border-r-[1.5px] before:border-b-[1.5px] before:border-tsb15-mut before:-rotate-45 before:transition-transform before:duration-150 group-open/l2:before:rotate-45 group-has-[a[aria-current=page]]/l2:text-tsb15-acc hover:bg-tsb15-surf2 focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2 motion-reduce:before:transition-none">Deployments</summary>
<ul class="list-none m-0 p-0">
<li><a class="flex items-center min-w-0 min-h-11 py-2 pr-2.5 pl-7.5 text-[0.88rem] text-tsb15-mut no-underline hover:bg-tsb15-surf2 focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2 aria-[current=page]:bg-tsb15-accsoft aria-[current=page]:text-tsb15-acc aria-[current=page]:font-semibold" href="#tsb-15-main">Promotion policy</a></li>
<li><a class="flex items-center min-w-0 min-h-11 py-2 pr-2.5 pl-7.5 text-[0.88rem] text-tsb15-mut no-underline hover:bg-tsb15-surf2 focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2 aria-[current=page]:bg-tsb15-accsoft aria-[current=page]:text-tsb15-acc aria-[current=page]:font-semibold" href="#tsb-15-main" aria-current="page">Rollback runbook</a></li>
<li><a class="flex items-center min-w-0 min-h-11 py-2 pr-2.5 pl-7.5 text-[0.88rem] text-tsb15-mut no-underline hover:bg-tsb15-surf2 focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2 aria-[current=page]:bg-tsb15-accsoft aria-[current=page]:text-tsb15-acc aria-[current=page]:font-semibold" href="#tsb-15-main">Region failover</a></li>
</ul>
</details>
<details class="group/l2">
<summary class="flex items-center gap-2 min-h-11 py-2 pr-2.5 pl-4.5 text-[0.89rem] font-medium cursor-pointer list-none [&::-webkit-details-marker]:hidden before:content-[''] before:flex-none before:w-1.5 before:h-1.5 before:border-r-[1.5px] before:border-b-[1.5px] before:border-tsb15-mut before:-rotate-45 before:transition-transform before:duration-150 group-open/l2:before:rotate-45 group-has-[a[aria-current=page]]/l2:text-tsb15-acc hover:bg-tsb15-surf2 focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2 motion-reduce:before:transition-none">On call</summary>
<ul class="list-none m-0 p-0">
<li><a class="flex items-center min-w-0 min-h-11 py-2 pr-2.5 pl-7.5 text-[0.88rem] text-tsb15-mut no-underline hover:bg-tsb15-surf2 focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2" href="#tsb-15-main">Escalation ladder</a></li>
<li><a class="flex items-center min-w-0 min-h-11 py-2 pr-2.5 pl-7.5 text-[0.88rem] text-tsb15-mut no-underline hover:bg-tsb15-surf2 focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2" href="#tsb-15-main">Incident review</a></li>
</ul>
</details>
</div>
</details>
<details class="group/l1">
<summary class="flex items-center gap-2 min-h-11 px-2.5 py-2 text-[0.91rem] font-semibold cursor-pointer list-none [&::-webkit-details-marker]:hidden before:content-[''] before:flex-none before:w-1.5 before:h-1.5 before:border-r-[1.5px] before:border-b-[1.5px] before:border-tsb15-mut before:-rotate-45 before:transition-transform before:duration-150 group-open/l1:before:rotate-45 group-has-[a[aria-current=page]]/l1:text-tsb15-acc hover:bg-tsb15-surf2 focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2 motion-reduce:before:transition-none">People</summary>
<div class="ml-2.5 border-l border-tsb15-line">
<details class="group/l2">
<summary class="flex items-center gap-2 min-h-11 py-2 pr-2.5 pl-4.5 text-[0.89rem] font-medium cursor-pointer list-none [&::-webkit-details-marker]:hidden before:content-[''] before:flex-none before:w-1.5 before:h-1.5 before:border-r-[1.5px] before:border-b-[1.5px] before:border-tsb15-mut before:-rotate-45 before:transition-transform before:duration-150 group-open/l2:before:rotate-45 group-has-[a[aria-current=page]]/l2:text-tsb15-acc hover:bg-tsb15-surf2 focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2 motion-reduce:before:transition-none">Onboarding</summary>
<ul class="list-none m-0 p-0">
<li><a class="flex items-center min-w-0 min-h-11 py-2 pr-2.5 pl-7.5 text-[0.88rem] text-tsb15-mut no-underline hover:bg-tsb15-surf2 focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2" href="#tsb-15-main">First week</a></li>
<li><a class="flex items-center min-w-0 min-h-11 py-2 pr-2.5 pl-7.5 text-[0.88rem] text-tsb15-mut no-underline hover:bg-tsb15-surf2 focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2" href="#tsb-15-main">Access requests</a></li>
</ul>
</details>
<details class="group/l2">
<summary class="flex items-center gap-2 min-h-11 py-2 pr-2.5 pl-4.5 text-[0.89rem] font-medium cursor-pointer list-none [&::-webkit-details-marker]:hidden before:content-[''] before:flex-none before:w-1.5 before:h-1.5 before:border-r-[1.5px] before:border-b-[1.5px] before:border-tsb15-mut before:-rotate-45 before:transition-transform before:duration-150 group-open/l2:before:rotate-45 group-has-[a[aria-current=page]]/l2:text-tsb15-acc hover:bg-tsb15-surf2 focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2 motion-reduce:before:transition-none">Reviews</summary>
<ul class="list-none m-0 p-0">
<li><a class="flex items-center min-w-0 min-h-11 py-2 pr-2.5 pl-7.5 text-[0.88rem] text-tsb15-mut no-underline hover:bg-tsb15-surf2 focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2" href="#tsb-15-main">Cycle calendar</a></li>
<li><a class="flex items-center min-w-0 min-h-11 py-2 pr-2.5 pl-7.5 text-[0.88rem] text-tsb15-mut no-underline hover:bg-tsb15-surf2 focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2" href="#tsb-15-main">Writing feedback</a></li>
</ul>
</details>
</div>
</details>
</nav>
<main class="min-w-0 content-start px-5 pt-6 pb-10 r48:px-9 r48:pt-10 r48:pb-12" id="tsb-15-main">
<p class="m-0 mb-1 text-[0.82rem] text-tsb15-mut">Operations / Deployments</p>
<h2 class="m-0 mb-4.5 font-tsb15-display text-[clamp(1.45rem,3.6vw,2rem)] leading-[1.1]">Rollback runbook</h2>
<nav class="mb-5 px-3.5 py-3 max-w-[44rem] bg-tsb15-surf2" aria-label="On this page">
<p class="m-0 mb-2 text-[0.7rem] tracking-[0.1em] uppercase text-tsb15-mut">On this page</p>
<ul class="list-none m-0 p-0 flex flex-wrap gap-1.5">
<li><a class="inline-flex items-center min-h-11 px-3.5 bg-tsb15-surf border border-tsb15-line text-[0.87rem] text-tsb15-ink no-underline hover:bg-tsb15-bg focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2 aria-[current=page]:bg-tsb15-acc aria-[current=page]:border-tsb15-acc aria-[current=page]:text-tsb15-onacc aria-[current=page]:font-semibold" href="#tsb-15-main" aria-current="page">Before you start</a></li>
<li><a class="inline-flex items-center min-h-11 px-3.5 bg-tsb15-surf border border-tsb15-line text-[0.87rem] text-tsb15-ink no-underline hover:bg-tsb15-bg focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2" href="#tsb-15-main">Pointer move</a></li>
<li><a class="inline-flex items-center min-h-11 px-3.5 bg-tsb15-surf border border-tsb15-line text-[0.87rem] text-tsb15-ink no-underline hover:bg-tsb15-bg focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2" href="#tsb-15-main">Verifying</a></li>
<li><a class="inline-flex items-center min-h-11 px-3.5 bg-tsb15-surf border border-tsb15-line text-[0.87rem] text-tsb15-ink no-underline hover:bg-tsb15-bg focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2" href="#tsb-15-main">Post-mortem</a></li>
</ul>
</nav>
<p class="m-0 mb-3 max-w-[40rem] text-[0.94rem] text-tsb15-mut text-pretty">Promotion is a pointer move, so a rollback is the same operation in reverse and completes in under a second. The slow part is verification — confirm the health checks on the previous build before you announce it.</p>
<p class="m-0 mb-3 max-w-[40rem] text-[0.94rem] text-tsb15-mut text-pretty">Announce in the incident channel first, then update the status page. If the previous build is more than seven days old, check the migration log before promoting it.</p>
</main>
</div>
</section>@import "tailwindcss";
@theme {
--color-tsb15-bg: #f7f4ee;
--color-tsb15-bg: oklch(96% 0.012 90);
--color-tsb15-surf: #fffdf8;
--color-tsb15-surf: oklch(99% 0.008 90);
--color-tsb15-surf2: #efeade;
--color-tsb15-surf2: oklch(93% 0.018 90);
--color-tsb15-line: #ddd6c6;
--color-tsb15-line: oklch(87% 0.024 90);
--color-tsb15-ink: #201d16;
--color-tsb15-ink: oklch(23% 0.014 80);
--color-tsb15-mut: #6a6252;
--color-tsb15-mut: oklch(47% 0.02 85);
--color-tsb15-acc: #4c5f2a;
--color-tsb15-acc: oklch(45% 0.1 122);
--color-tsb15-accsoft: #e4ead2;
--color-tsb15-accsoft: oklch(92% 0.03 122);
--color-tsb15-onacc: #fffdf8;
--color-tsb15-onacc: oklch(99% 0.008 90);
--font-tsb15-sans: system-ui, -apple-system, 'Segoe UI', sans-serif;
--font-tsb15-display: 'Karla', Optima, Candara, 'Gill Sans', system-ui, sans-serif;
--breakpoint-r48: 48rem;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's
w-full min-h-svh. */
.tsb-15 {
width: 100%;
min-height: 100svh;
}
.tsb-15 *,
.tsb-15 *::before,
.tsb-15 *::after {
box-sizing: border-box;
}
/* Token flip for the pure-CSS theme toggle, plus the two mandatory mirror blocks. */
.tsb-15:has(.tsb-15-theme-input:checked) {
--color-tsb15-bg: #14130f;
--color-tsb15-surf: #1b1a14;
--color-tsb15-surf2: #23211a;
--color-tsb15-line: #332f25;
--color-tsb15-ink: #f0ece1;
--color-tsb15-mut: #a49b87;
--color-tsb15-acc: #b6cf80;
--color-tsb15-acc: oklch(82% 0.12 122);
--color-tsb15-accsoft: #262d17;
--color-tsb15-onacc: #1b1a14;
}
@media (prefers-color-scheme: dark) {
.tsb-15 {
--color-tsb15-bg: #14130f;
--color-tsb15-surf: #1b1a14;
--color-tsb15-surf2: #23211a;
--color-tsb15-line: #332f25;
--color-tsb15-ink: #f0ece1;
--color-tsb15-mut: #a49b87;
--color-tsb15-acc: #b6cf80;
--color-tsb15-acc: oklch(82% 0.12 122);
--color-tsb15-accsoft: #262d17;
--color-tsb15-onacc: #1b1a14;
}
.tsb-15:has(.tsb-15-theme-input:checked) {
--color-tsb15-bg: #f7f4ee;
--color-tsb15-surf: #fffdf8;
--color-tsb15-surf2: #efeade;
--color-tsb15-line: #ddd6c6;
--color-tsb15-ink: #201d16;
--color-tsb15-mut: #6a6252;
--color-tsb15-acc: #4c5f2a;
--color-tsb15-acc: oklch(45% 0.1 122);
--color-tsb15-accsoft: #e4ead2;
--color-tsb15-onacc: #fffdf8;
}
}
@media (prefers-color-scheme: light) {
.tsb-15:has(.tsb-15-theme-input:checked) {
--color-tsb15-bg: #14130f;
--color-tsb15-surf: #1b1a14;
--color-tsb15-surf2: #23211a;
--color-tsb15-line: #332f25;
--color-tsb15-ink: #f0ece1;
--color-tsb15-mut: #a49b87;
--color-tsb15-acc: #b6cf80;
--color-tsb15-acc: oklch(82% 0.12 122);
--color-tsb15-accsoft: #262d17;
--color-tsb15-onacc: #1b1a14;
}
}@import "tailwindcss";
@theme {
--color-tsb15-bg: #f7f4ee;
--color-tsb15-bg: oklch(96% 0.012 90);
--color-tsb15-surf: #fffdf8;
--color-tsb15-surf: oklch(99% 0.008 90);
--color-tsb15-surf2: #efeade;
--color-tsb15-surf2: oklch(93% 0.018 90);
--color-tsb15-line: #ddd6c6;
--color-tsb15-line: oklch(87% 0.024 90);
--color-tsb15-ink: #201d16;
--color-tsb15-ink: oklch(23% 0.014 80);
--color-tsb15-mut: #6a6252;
--color-tsb15-mut: oklch(47% 0.02 85);
--color-tsb15-acc: #4c5f2a;
--color-tsb15-acc: oklch(45% 0.1 122);
--color-tsb15-accsoft: #e4ead2;
--color-tsb15-accsoft: oklch(92% 0.03 122);
--color-tsb15-onacc: #fffdf8;
--color-tsb15-onacc: oklch(99% 0.008 90);
--font-tsb15-sans: system-ui, -apple-system, 'Segoe UI', sans-serif;
--font-tsb15-display: 'Karla', Optima, Candara, 'Gill Sans', system-ui, sans-serif;
--breakpoint-r48: 48rem;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's
w-full min-h-svh. */
.tsb-15 {
width: 100%;
min-height: 100svh;
}
.tsb-15 *,
.tsb-15 *::before,
.tsb-15 *::after {
box-sizing: border-box;
}
/* Token flip for the pure-CSS theme toggle, plus the two mandatory mirror blocks. */
.tsb-15:has(.tsb-15-theme-input:checked) {
--color-tsb15-bg: #14130f;
--color-tsb15-surf: #1b1a14;
--color-tsb15-surf2: #23211a;
--color-tsb15-line: #332f25;
--color-tsb15-ink: #f0ece1;
--color-tsb15-mut: #a49b87;
--color-tsb15-acc: #b6cf80;
--color-tsb15-acc: oklch(82% 0.12 122);
--color-tsb15-accsoft: #262d17;
--color-tsb15-onacc: #1b1a14;
}
@media (prefers-color-scheme: dark) {
.tsb-15 {
--color-tsb15-bg: #14130f;
--color-tsb15-surf: #1b1a14;
--color-tsb15-surf2: #23211a;
--color-tsb15-line: #332f25;
--color-tsb15-ink: #f0ece1;
--color-tsb15-mut: #a49b87;
--color-tsb15-acc: #b6cf80;
--color-tsb15-acc: oklch(82% 0.12 122);
--color-tsb15-accsoft: #262d17;
--color-tsb15-onacc: #1b1a14;
}
.tsb-15:has(.tsb-15-theme-input:checked) {
--color-tsb15-bg: #f7f4ee;
--color-tsb15-surf: #fffdf8;
--color-tsb15-surf2: #efeade;
--color-tsb15-line: #ddd6c6;
--color-tsb15-ink: #201d16;
--color-tsb15-mut: #6a6252;
--color-tsb15-acc: #4c5f2a;
--color-tsb15-acc: oklch(45% 0.1 122);
--color-tsb15-accsoft: #e4ead2;
--color-tsb15-onacc: #fffdf8;
}
}
@media (prefers-color-scheme: light) {
.tsb-15:has(.tsb-15-theme-input:checked) {
--color-tsb15-bg: #14130f;
--color-tsb15-surf: #1b1a14;
--color-tsb15-surf2: #23211a;
--color-tsb15-line: #332f25;
--color-tsb15-ink: #f0ece1;
--color-tsb15-mut: #a49b87;
--color-tsb15-acc: #b6cf80;
--color-tsb15-acc: oklch(82% 0.12 122);
--color-tsb15-accsoft: #262d17;
--color-tsb15-onacc: #1b1a14;
}
}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-tsb15-bg: #f7f4ee;
--color-tsb15-bg: oklch(96% 0.012 90);
--color-tsb15-surf: #fffdf8;
--color-tsb15-surf: oklch(99% 0.008 90);
--color-tsb15-surf2: #efeade;
--color-tsb15-surf2: oklch(93% 0.018 90);
--color-tsb15-line: #ddd6c6;
--color-tsb15-line: oklch(87% 0.024 90);
--color-tsb15-ink: #201d16;
--color-tsb15-ink: oklch(23% 0.014 80);
--color-tsb15-mut: #6a6252;
--color-tsb15-mut: oklch(47% 0.02 85);
--color-tsb15-acc: #4c5f2a;
--color-tsb15-acc: oklch(45% 0.1 122);
--color-tsb15-accsoft: #e4ead2;
--color-tsb15-accsoft: oklch(92% 0.03 122);
--color-tsb15-onacc: #fffdf8;
--color-tsb15-onacc: oklch(99% 0.008 90);
--font-tsb15-sans: system-ui, -apple-system, 'Segoe UI', sans-serif;
--font-tsb15-display: 'Karla', Optima, Candara, 'Gill Sans', system-ui, sans-serif;
--breakpoint-r48: 48rem;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's
w-full min-h-svh. */
.tsb-15 {
width: 100%;
min-height: 100svh;
}
.tsb-15 *,
.tsb-15 *::before,
.tsb-15 *::after {
box-sizing: border-box;
}
/* Token flip for the pure-CSS theme toggle, plus the two mandatory mirror blocks. */
.tsb-15:has(.tsb-15-theme-input:checked) {
--color-tsb15-bg: #14130f;
--color-tsb15-surf: #1b1a14;
--color-tsb15-surf2: #23211a;
--color-tsb15-line: #332f25;
--color-tsb15-ink: #f0ece1;
--color-tsb15-mut: #a49b87;
--color-tsb15-acc: #b6cf80;
--color-tsb15-acc: oklch(82% 0.12 122);
--color-tsb15-accsoft: #262d17;
--color-tsb15-onacc: #1b1a14;
}
@media (prefers-color-scheme: dark) {
.tsb-15 {
--color-tsb15-bg: #14130f;
--color-tsb15-surf: #1b1a14;
--color-tsb15-surf2: #23211a;
--color-tsb15-line: #332f25;
--color-tsb15-ink: #f0ece1;
--color-tsb15-mut: #a49b87;
--color-tsb15-acc: #b6cf80;
--color-tsb15-acc: oklch(82% 0.12 122);
--color-tsb15-accsoft: #262d17;
--color-tsb15-onacc: #1b1a14;
}
.tsb-15:has(.tsb-15-theme-input:checked) {
--color-tsb15-bg: #f7f4ee;
--color-tsb15-surf: #fffdf8;
--color-tsb15-surf2: #efeade;
--color-tsb15-line: #ddd6c6;
--color-tsb15-ink: #201d16;
--color-tsb15-mut: #6a6252;
--color-tsb15-acc: #4c5f2a;
--color-tsb15-acc: oklch(45% 0.1 122);
--color-tsb15-accsoft: #e4ead2;
--color-tsb15-onacc: #fffdf8;
}
}
@media (prefers-color-scheme: light) {
.tsb-15:has(.tsb-15-theme-input:checked) {
--color-tsb15-bg: #14130f;
--color-tsb15-surf: #1b1a14;
--color-tsb15-surf2: #23211a;
--color-tsb15-line: #332f25;
--color-tsb15-ink: #f0ece1;
--color-tsb15-mut: #a49b87;
--color-tsb15-acc: #b6cf80;
--color-tsb15-acc: oklch(82% 0.12 122);
--color-tsb15-accsoft: #262d17;
--color-tsb15-onacc: #1b1a14;
}
}@import "tailwindcss";
@theme {
--color-tsb15-bg: #f7f4ee;
--color-tsb15-bg: oklch(96% 0.012 90);
--color-tsb15-surf: #fffdf8;
--color-tsb15-surf: oklch(99% 0.008 90);
--color-tsb15-surf2: #efeade;
--color-tsb15-surf2: oklch(93% 0.018 90);
--color-tsb15-line: #ddd6c6;
--color-tsb15-line: oklch(87% 0.024 90);
--color-tsb15-ink: #201d16;
--color-tsb15-ink: oklch(23% 0.014 80);
--color-tsb15-mut: #6a6252;
--color-tsb15-mut: oklch(47% 0.02 85);
--color-tsb15-acc: #4c5f2a;
--color-tsb15-acc: oklch(45% 0.1 122);
--color-tsb15-accsoft: #e4ead2;
--color-tsb15-accsoft: oklch(92% 0.03 122);
--color-tsb15-onacc: #fffdf8;
--color-tsb15-onacc: oklch(99% 0.008 90);
--font-tsb15-sans: system-ui, -apple-system, 'Segoe UI', sans-serif;
--font-tsb15-display: 'Karla', Optima, Candara, 'Gill Sans', system-ui, sans-serif;
--breakpoint-r48: 48rem;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's
w-full min-h-svh. */
.tsb-15 {
width: 100%;
min-height: 100svh;
}
.tsb-15 *,
.tsb-15 *::before,
.tsb-15 *::after {
box-sizing: border-box;
}
/* Token flip for the pure-CSS theme toggle, plus the two mandatory mirror blocks. */
.tsb-15:has(.tsb-15-theme-input:checked) {
--color-tsb15-bg: #14130f;
--color-tsb15-surf: #1b1a14;
--color-tsb15-surf2: #23211a;
--color-tsb15-line: #332f25;
--color-tsb15-ink: #f0ece1;
--color-tsb15-mut: #a49b87;
--color-tsb15-acc: #b6cf80;
--color-tsb15-acc: oklch(82% 0.12 122);
--color-tsb15-accsoft: #262d17;
--color-tsb15-onacc: #1b1a14;
}
@media (prefers-color-scheme: dark) {
.tsb-15 {
--color-tsb15-bg: #14130f;
--color-tsb15-surf: #1b1a14;
--color-tsb15-surf2: #23211a;
--color-tsb15-line: #332f25;
--color-tsb15-ink: #f0ece1;
--color-tsb15-mut: #a49b87;
--color-tsb15-acc: #b6cf80;
--color-tsb15-acc: oklch(82% 0.12 122);
--color-tsb15-accsoft: #262d17;
--color-tsb15-onacc: #1b1a14;
}
.tsb-15:has(.tsb-15-theme-input:checked) {
--color-tsb15-bg: #f7f4ee;
--color-tsb15-surf: #fffdf8;
--color-tsb15-surf2: #efeade;
--color-tsb15-line: #ddd6c6;
--color-tsb15-ink: #201d16;
--color-tsb15-mut: #6a6252;
--color-tsb15-acc: #4c5f2a;
--color-tsb15-acc: oklch(45% 0.1 122);
--color-tsb15-accsoft: #e4ead2;
--color-tsb15-onacc: #fffdf8;
}
}
@media (prefers-color-scheme: light) {
.tsb-15:has(.tsb-15-theme-input:checked) {
--color-tsb15-bg: #14130f;
--color-tsb15-surf: #1b1a14;
--color-tsb15-surf2: #23211a;
--color-tsb15-line: #332f25;
--color-tsb15-ink: #f0ece1;
--color-tsb15-mut: #a49b87;
--color-tsb15-acc: #b6cf80;
--color-tsb15-acc: oklch(82% 0.12 122);
--color-tsb15-accsoft: #262d17;
--color-tsb15-onacc: #1b1a14;
}
}<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=Karla:wght@400;500;600;700&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=Karla:wght@400;500;600;700&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
- Named groups are the headline of this port.
group/l1andgroup/l2withgroup-open/l1:/group-open/l2:give each level its own scope, which is what the CSS version got from.l1[open] > .sumchild selectors. - The chevron is a pseudo-element utility set rather than an SVG:
before:content-[''], two border sides, a rotation and a transition. Tailwind expresses::beforeperfectly well; what it cannot do is a pseudo-element with dynamic content. group-has-[a[aria-current=page]]/l1:text-tsb15-accis a scoped:has()— the same idea as demos 09 and 14, but named, so the level-1 summary does not react to a level-2 match through the wrong ancestor.- The branch guide is
ml-2.5 border-lon the wrapper<div>, which is why the markup keeps that div: it is the indentation AND the guide line in one element. - Every summary class list is long, and deliberately not shortened with an
@applycomponent class — the collection's premise is that the state lives on the element, and a component class hides the level scoping that this demo is about. - The 48rem breakpoint is kept as
--breakpoint-r48rather than mapped tomd(which is also 48rem) so every demo in the collection reads its own number consistently.
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: Pure Tailwind Multi Level Sidebar Menu Disclosure Pattern
Source: https://codefronts.com/navigation/tailwind-css-sidebars/pure-tailwind-multi-level-sidebar-menu-disclosure-pattern/
Three levels deep, and the demo exists to document the one Tailwind trap that bites here: group-open: matches through EVERY open ancestor, so a nested tree rotates chevrons that are still closed. The fix is named groups — group/l1 and group/l2 with group-open/l1: and group-open/l2: — and once they are named, three levels cost no more than one.
## HTML
```html
<section class="tsb-15 block relative w-full min-h-svh bg-tsb15-bg text-tsb15-ink font-tsb15-sans leading-[1.55]" aria-label="Basalt knowledge base">
<div class="absolute top-3.5 right-3.5 z-60">
<input class="tsb-15-theme-input peer absolute inset-0 w-full h-full m-0 opacity-0 cursor-pointer" type="checkbox" id="tsb-15-theme">
<label class="grid place-items-center w-11 h-11 border border-tsb15-line bg-tsb15-surf text-tsb15-ink cursor-pointer peer-focus-visible:outline-2 peer-focus-visible:outline-tsb15-acc peer-focus-visible:outline-offset-2" for="tsb-15-theme">
<svg class="w-4.5 h-4.5" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="8.5" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M12 3.5a8.5 8.5 0 0 0 0 17Z" fill="currentColor"/></svg>
<span class="absolute w-px h-px overflow-hidden whitespace-nowrap [clip-path:inset(50%)]">Toggle dark theme</span>
</label>
</div>
<div class="grid grid-cols-[minmax(0,1fr)] min-h-svh bg-tsb15-surf r48:grid-cols-[minmax(0,17.5rem)_minmax(0,1fr)]">
<nav class="min-w-0 px-2.5 pt-4 pb-5 border-b border-tsb15-line r48:border-b-0 r48:border-r r48:border-tsb15-line" aria-label="Basalt knowledge base">
<p class="m-0 mb-3 px-2 font-tsb15-display text-[1.05rem] tracking-[0.02em]">Handbook</p>
<ul class="list-none m-0 p-0">
<li><a class="flex items-center min-w-0 min-h-11 px-2.5 py-2 text-[0.91rem] text-tsb15-ink no-underline hover:bg-tsb15-surf2 focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2" href="#tsb-15-main">Start here</a></li>
</ul>
<details class="group/l1" open>
<summary class="flex items-center gap-2 min-h-11 px-2.5 py-2 text-[0.91rem] font-semibold cursor-pointer list-none [&::-webkit-details-marker]:hidden before:content-[''] before:flex-none before:w-1.5 before:h-1.5 before:border-r-[1.5px] before:border-b-[1.5px] before:border-tsb15-mut before:-rotate-45 before:transition-transform before:duration-150 group-open/l1:before:rotate-45 group-has-[a[aria-current=page]]/l1:text-tsb15-acc hover:bg-tsb15-surf2 focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2 motion-reduce:before:transition-none">Operations</summary>
<div class="ml-2.5 border-l border-tsb15-line">
<details class="group/l2" open>
<summary class="flex items-center gap-2 min-h-11 py-2 pr-2.5 pl-4.5 text-[0.89rem] font-medium cursor-pointer list-none [&::-webkit-details-marker]:hidden before:content-[''] before:flex-none before:w-1.5 before:h-1.5 before:border-r-[1.5px] before:border-b-[1.5px] before:border-tsb15-mut before:-rotate-45 before:transition-transform before:duration-150 group-open/l2:before:rotate-45 group-has-[a[aria-current=page]]/l2:text-tsb15-acc hover:bg-tsb15-surf2 focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2 motion-reduce:before:transition-none">Deployments</summary>
<ul class="list-none m-0 p-0">
<li><a class="flex items-center min-w-0 min-h-11 py-2 pr-2.5 pl-7.5 text-[0.88rem] text-tsb15-mut no-underline hover:bg-tsb15-surf2 focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2 aria-[current=page]:bg-tsb15-accsoft aria-[current=page]:text-tsb15-acc aria-[current=page]:font-semibold" href="#tsb-15-main">Promotion policy</a></li>
<li><a class="flex items-center min-w-0 min-h-11 py-2 pr-2.5 pl-7.5 text-[0.88rem] text-tsb15-mut no-underline hover:bg-tsb15-surf2 focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2 aria-[current=page]:bg-tsb15-accsoft aria-[current=page]:text-tsb15-acc aria-[current=page]:font-semibold" href="#tsb-15-main" aria-current="page">Rollback runbook</a></li>
<li><a class="flex items-center min-w-0 min-h-11 py-2 pr-2.5 pl-7.5 text-[0.88rem] text-tsb15-mut no-underline hover:bg-tsb15-surf2 focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2 aria-[current=page]:bg-tsb15-accsoft aria-[current=page]:text-tsb15-acc aria-[current=page]:font-semibold" href="#tsb-15-main">Region failover</a></li>
</ul>
</details>
<details class="group/l2">
<summary class="flex items-center gap-2 min-h-11 py-2 pr-2.5 pl-4.5 text-[0.89rem] font-medium cursor-pointer list-none [&::-webkit-details-marker]:hidden before:content-[''] before:flex-none before:w-1.5 before:h-1.5 before:border-r-[1.5px] before:border-b-[1.5px] before:border-tsb15-mut before:-rotate-45 before:transition-transform before:duration-150 group-open/l2:before:rotate-45 group-has-[a[aria-current=page]]/l2:text-tsb15-acc hover:bg-tsb15-surf2 focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2 motion-reduce:before:transition-none">On call</summary>
<ul class="list-none m-0 p-0">
<li><a class="flex items-center min-w-0 min-h-11 py-2 pr-2.5 pl-7.5 text-[0.88rem] text-tsb15-mut no-underline hover:bg-tsb15-surf2 focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2" href="#tsb-15-main">Escalation ladder</a></li>
<li><a class="flex items-center min-w-0 min-h-11 py-2 pr-2.5 pl-7.5 text-[0.88rem] text-tsb15-mut no-underline hover:bg-tsb15-surf2 focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2" href="#tsb-15-main">Incident review</a></li>
</ul>
</details>
</div>
</details>
<details class="group/l1">
<summary class="flex items-center gap-2 min-h-11 px-2.5 py-2 text-[0.91rem] font-semibold cursor-pointer list-none [&::-webkit-details-marker]:hidden before:content-[''] before:flex-none before:w-1.5 before:h-1.5 before:border-r-[1.5px] before:border-b-[1.5px] before:border-tsb15-mut before:-rotate-45 before:transition-transform before:duration-150 group-open/l1:before:rotate-45 group-has-[a[aria-current=page]]/l1:text-tsb15-acc hover:bg-tsb15-surf2 focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2 motion-reduce:before:transition-none">People</summary>
<div class="ml-2.5 border-l border-tsb15-line">
<details class="group/l2">
<summary class="flex items-center gap-2 min-h-11 py-2 pr-2.5 pl-4.5 text-[0.89rem] font-medium cursor-pointer list-none [&::-webkit-details-marker]:hidden before:content-[''] before:flex-none before:w-1.5 before:h-1.5 before:border-r-[1.5px] before:border-b-[1.5px] before:border-tsb15-mut before:-rotate-45 before:transition-transform before:duration-150 group-open/l2:before:rotate-45 group-has-[a[aria-current=page]]/l2:text-tsb15-acc hover:bg-tsb15-surf2 focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2 motion-reduce:before:transition-none">Onboarding</summary>
<ul class="list-none m-0 p-0">
<li><a class="flex items-center min-w-0 min-h-11 py-2 pr-2.5 pl-7.5 text-[0.88rem] text-tsb15-mut no-underline hover:bg-tsb15-surf2 focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2" href="#tsb-15-main">First week</a></li>
<li><a class="flex items-center min-w-0 min-h-11 py-2 pr-2.5 pl-7.5 text-[0.88rem] text-tsb15-mut no-underline hover:bg-tsb15-surf2 focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2" href="#tsb-15-main">Access requests</a></li>
</ul>
</details>
<details class="group/l2">
<summary class="flex items-center gap-2 min-h-11 py-2 pr-2.5 pl-4.5 text-[0.89rem] font-medium cursor-pointer list-none [&::-webkit-details-marker]:hidden before:content-[''] before:flex-none before:w-1.5 before:h-1.5 before:border-r-[1.5px] before:border-b-[1.5px] before:border-tsb15-mut before:-rotate-45 before:transition-transform before:duration-150 group-open/l2:before:rotate-45 group-has-[a[aria-current=page]]/l2:text-tsb15-acc hover:bg-tsb15-surf2 focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2 motion-reduce:before:transition-none">Reviews</summary>
<ul class="list-none m-0 p-0">
<li><a class="flex items-center min-w-0 min-h-11 py-2 pr-2.5 pl-7.5 text-[0.88rem] text-tsb15-mut no-underline hover:bg-tsb15-surf2 focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2" href="#tsb-15-main">Cycle calendar</a></li>
<li><a class="flex items-center min-w-0 min-h-11 py-2 pr-2.5 pl-7.5 text-[0.88rem] text-tsb15-mut no-underline hover:bg-tsb15-surf2 focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2" href="#tsb-15-main">Writing feedback</a></li>
</ul>
</details>
</div>
</details>
</nav>
<main class="min-w-0 content-start px-5 pt-6 pb-10 r48:px-9 r48:pt-10 r48:pb-12" id="tsb-15-main">
<p class="m-0 mb-1 text-[0.82rem] text-tsb15-mut">Operations / Deployments</p>
<h2 class="m-0 mb-4.5 font-tsb15-display text-[clamp(1.45rem,3.6vw,2rem)] leading-[1.1]">Rollback runbook</h2>
<nav class="mb-5 px-3.5 py-3 max-w-[44rem] bg-tsb15-surf2" aria-label="On this page">
<p class="m-0 mb-2 text-[0.7rem] tracking-[0.1em] uppercase text-tsb15-mut">On this page</p>
<ul class="list-none m-0 p-0 flex flex-wrap gap-1.5">
<li><a class="inline-flex items-center min-h-11 px-3.5 bg-tsb15-surf border border-tsb15-line text-[0.87rem] text-tsb15-ink no-underline hover:bg-tsb15-bg focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2 aria-[current=page]:bg-tsb15-acc aria-[current=page]:border-tsb15-acc aria-[current=page]:text-tsb15-onacc aria-[current=page]:font-semibold" href="#tsb-15-main" aria-current="page">Before you start</a></li>
<li><a class="inline-flex items-center min-h-11 px-3.5 bg-tsb15-surf border border-tsb15-line text-[0.87rem] text-tsb15-ink no-underline hover:bg-tsb15-bg focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2" href="#tsb-15-main">Pointer move</a></li>
<li><a class="inline-flex items-center min-h-11 px-3.5 bg-tsb15-surf border border-tsb15-line text-[0.87rem] text-tsb15-ink no-underline hover:bg-tsb15-bg focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2" href="#tsb-15-main">Verifying</a></li>
<li><a class="inline-flex items-center min-h-11 px-3.5 bg-tsb15-surf border border-tsb15-line text-[0.87rem] text-tsb15-ink no-underline hover:bg-tsb15-bg focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2" href="#tsb-15-main">Post-mortem</a></li>
</ul>
</nav>
<p class="m-0 mb-3 max-w-[40rem] text-[0.94rem] text-tsb15-mut text-pretty">Promotion is a pointer move, so a rollback is the same operation in reverse and completes in under a second. The slow part is verification — confirm the health checks on the previous build before you announce it.</p>
<p class="m-0 mb-3 max-w-[40rem] text-[0.94rem] text-tsb15-mut text-pretty">Announce in the incident channel first, then update the status page. If the previous build is more than seven days old, check the migration log before promoting it.</p>
</main>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tsb15-bg: #f7f4ee;
--color-tsb15-bg: oklch(96% 0.012 90);
--color-tsb15-surf: #fffdf8;
--color-tsb15-surf: oklch(99% 0.008 90);
--color-tsb15-surf2: #efeade;
--color-tsb15-surf2: oklch(93% 0.018 90);
--color-tsb15-line: #ddd6c6;
--color-tsb15-line: oklch(87% 0.024 90);
--color-tsb15-ink: #201d16;
--color-tsb15-ink: oklch(23% 0.014 80);
--color-tsb15-mut: #6a6252;
--color-tsb15-mut: oklch(47% 0.02 85);
--color-tsb15-acc: #4c5f2a;
--color-tsb15-acc: oklch(45% 0.1 122);
--color-tsb15-accsoft: #e4ead2;
--color-tsb15-accsoft: oklch(92% 0.03 122);
--color-tsb15-onacc: #fffdf8;
--color-tsb15-onacc: oklch(99% 0.008 90);
--font-tsb15-sans: system-ui, -apple-system, 'Segoe UI', sans-serif;
--font-tsb15-display: 'Karla', Optima, Candara, 'Gill Sans', system-ui, sans-serif;
--breakpoint-r48: 48rem;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's
w-full min-h-svh. */
.tsb-15 {
width: 100%;
min-height: 100svh;
}
.tsb-15 *,
.tsb-15 *::before,
.tsb-15 *::after {
box-sizing: border-box;
}
/* Token flip for the pure-CSS theme toggle, plus the two mandatory mirror blocks. */
.tsb-15:has(.tsb-15-theme-input:checked) {
--color-tsb15-bg: #14130f;
--color-tsb15-surf: #1b1a14;
--color-tsb15-surf2: #23211a;
--color-tsb15-line: #332f25;
--color-tsb15-ink: #f0ece1;
--color-tsb15-mut: #a49b87;
--color-tsb15-acc: #b6cf80;
--color-tsb15-acc: oklch(82% 0.12 122);
--color-tsb15-accsoft: #262d17;
--color-tsb15-onacc: #1b1a14;
}
@media (prefers-color-scheme: dark) {
.tsb-15 {
--color-tsb15-bg: #14130f;
--color-tsb15-surf: #1b1a14;
--color-tsb15-surf2: #23211a;
--color-tsb15-line: #332f25;
--color-tsb15-ink: #f0ece1;
--color-tsb15-mut: #a49b87;
--color-tsb15-acc: #b6cf80;
--color-tsb15-acc: oklch(82% 0.12 122);
--color-tsb15-accsoft: #262d17;
--color-tsb15-onacc: #1b1a14;
}
.tsb-15:has(.tsb-15-theme-input:checked) {
--color-tsb15-bg: #f7f4ee;
--color-tsb15-surf: #fffdf8;
--color-tsb15-surf2: #efeade;
--color-tsb15-line: #ddd6c6;
--color-tsb15-ink: #201d16;
--color-tsb15-mut: #6a6252;
--color-tsb15-acc: #4c5f2a;
--color-tsb15-acc: oklch(45% 0.1 122);
--color-tsb15-accsoft: #e4ead2;
--color-tsb15-onacc: #fffdf8;
}
}
@media (prefers-color-scheme: light) {
.tsb-15:has(.tsb-15-theme-input:checked) {
--color-tsb15-bg: #14130f;
--color-tsb15-surf: #1b1a14;
--color-tsb15-surf2: #23211a;
--color-tsb15-line: #332f25;
--color-tsb15-ink: #f0ece1;
--color-tsb15-mut: #a49b87;
--color-tsb15-acc: #b6cf80;
--color-tsb15-acc: oklch(82% 0.12 122);
--color-tsb15-accsoft: #262d17;
--color-tsb15-onacc: #1b1a14;
}
}
```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: Pure Tailwind Multi Level Sidebar Menu Disclosure Pattern
Source: https://codefronts.com/navigation/tailwind-css-sidebars/pure-tailwind-multi-level-sidebar-menu-disclosure-pattern/
Three levels deep, and the demo exists to document the one Tailwind trap that bites here: group-open: matches through EVERY open ancestor, so a nested tree rotates chevrons that are still closed. The fix is named groups — group/l1 and group/l2 with group-open/l1: and group-open/l2: — and once they are named, three levels cost no more than one.
## HTML
```html
<section class="tsb-15 block relative w-full min-h-svh bg-tsb15-bg text-tsb15-ink font-tsb15-sans leading-[1.55]" aria-label="Basalt knowledge base">
<div class="absolute top-3.5 right-3.5 z-60">
<input class="tsb-15-theme-input peer absolute inset-0 w-full h-full m-0 opacity-0 cursor-pointer" type="checkbox" id="tsb-15-theme">
<label class="grid place-items-center w-11 h-11 border border-tsb15-line bg-tsb15-surf text-tsb15-ink cursor-pointer peer-focus-visible:outline-2 peer-focus-visible:outline-tsb15-acc peer-focus-visible:outline-offset-2" for="tsb-15-theme">
<svg class="w-4.5 h-4.5" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="8.5" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M12 3.5a8.5 8.5 0 0 0 0 17Z" fill="currentColor"/></svg>
<span class="absolute w-px h-px overflow-hidden whitespace-nowrap [clip-path:inset(50%)]">Toggle dark theme</span>
</label>
</div>
<div class="grid grid-cols-[minmax(0,1fr)] min-h-svh bg-tsb15-surf r48:grid-cols-[minmax(0,17.5rem)_minmax(0,1fr)]">
<nav class="min-w-0 px-2.5 pt-4 pb-5 border-b border-tsb15-line r48:border-b-0 r48:border-r r48:border-tsb15-line" aria-label="Basalt knowledge base">
<p class="m-0 mb-3 px-2 font-tsb15-display text-[1.05rem] tracking-[0.02em]">Handbook</p>
<ul class="list-none m-0 p-0">
<li><a class="flex items-center min-w-0 min-h-11 px-2.5 py-2 text-[0.91rem] text-tsb15-ink no-underline hover:bg-tsb15-surf2 focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2" href="#tsb-15-main">Start here</a></li>
</ul>
<details class="group/l1" open>
<summary class="flex items-center gap-2 min-h-11 px-2.5 py-2 text-[0.91rem] font-semibold cursor-pointer list-none [&::-webkit-details-marker]:hidden before:content-[''] before:flex-none before:w-1.5 before:h-1.5 before:border-r-[1.5px] before:border-b-[1.5px] before:border-tsb15-mut before:-rotate-45 before:transition-transform before:duration-150 group-open/l1:before:rotate-45 group-has-[a[aria-current=page]]/l1:text-tsb15-acc hover:bg-tsb15-surf2 focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2 motion-reduce:before:transition-none">Operations</summary>
<div class="ml-2.5 border-l border-tsb15-line">
<details class="group/l2" open>
<summary class="flex items-center gap-2 min-h-11 py-2 pr-2.5 pl-4.5 text-[0.89rem] font-medium cursor-pointer list-none [&::-webkit-details-marker]:hidden before:content-[''] before:flex-none before:w-1.5 before:h-1.5 before:border-r-[1.5px] before:border-b-[1.5px] before:border-tsb15-mut before:-rotate-45 before:transition-transform before:duration-150 group-open/l2:before:rotate-45 group-has-[a[aria-current=page]]/l2:text-tsb15-acc hover:bg-tsb15-surf2 focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2 motion-reduce:before:transition-none">Deployments</summary>
<ul class="list-none m-0 p-0">
<li><a class="flex items-center min-w-0 min-h-11 py-2 pr-2.5 pl-7.5 text-[0.88rem] text-tsb15-mut no-underline hover:bg-tsb15-surf2 focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2 aria-[current=page]:bg-tsb15-accsoft aria-[current=page]:text-tsb15-acc aria-[current=page]:font-semibold" href="#tsb-15-main">Promotion policy</a></li>
<li><a class="flex items-center min-w-0 min-h-11 py-2 pr-2.5 pl-7.5 text-[0.88rem] text-tsb15-mut no-underline hover:bg-tsb15-surf2 focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2 aria-[current=page]:bg-tsb15-accsoft aria-[current=page]:text-tsb15-acc aria-[current=page]:font-semibold" href="#tsb-15-main" aria-current="page">Rollback runbook</a></li>
<li><a class="flex items-center min-w-0 min-h-11 py-2 pr-2.5 pl-7.5 text-[0.88rem] text-tsb15-mut no-underline hover:bg-tsb15-surf2 focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2 aria-[current=page]:bg-tsb15-accsoft aria-[current=page]:text-tsb15-acc aria-[current=page]:font-semibold" href="#tsb-15-main">Region failover</a></li>
</ul>
</details>
<details class="group/l2">
<summary class="flex items-center gap-2 min-h-11 py-2 pr-2.5 pl-4.5 text-[0.89rem] font-medium cursor-pointer list-none [&::-webkit-details-marker]:hidden before:content-[''] before:flex-none before:w-1.5 before:h-1.5 before:border-r-[1.5px] before:border-b-[1.5px] before:border-tsb15-mut before:-rotate-45 before:transition-transform before:duration-150 group-open/l2:before:rotate-45 group-has-[a[aria-current=page]]/l2:text-tsb15-acc hover:bg-tsb15-surf2 focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2 motion-reduce:before:transition-none">On call</summary>
<ul class="list-none m-0 p-0">
<li><a class="flex items-center min-w-0 min-h-11 py-2 pr-2.5 pl-7.5 text-[0.88rem] text-tsb15-mut no-underline hover:bg-tsb15-surf2 focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2" href="#tsb-15-main">Escalation ladder</a></li>
<li><a class="flex items-center min-w-0 min-h-11 py-2 pr-2.5 pl-7.5 text-[0.88rem] text-tsb15-mut no-underline hover:bg-tsb15-surf2 focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2" href="#tsb-15-main">Incident review</a></li>
</ul>
</details>
</div>
</details>
<details class="group/l1">
<summary class="flex items-center gap-2 min-h-11 px-2.5 py-2 text-[0.91rem] font-semibold cursor-pointer list-none [&::-webkit-details-marker]:hidden before:content-[''] before:flex-none before:w-1.5 before:h-1.5 before:border-r-[1.5px] before:border-b-[1.5px] before:border-tsb15-mut before:-rotate-45 before:transition-transform before:duration-150 group-open/l1:before:rotate-45 group-has-[a[aria-current=page]]/l1:text-tsb15-acc hover:bg-tsb15-surf2 focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2 motion-reduce:before:transition-none">People</summary>
<div class="ml-2.5 border-l border-tsb15-line">
<details class="group/l2">
<summary class="flex items-center gap-2 min-h-11 py-2 pr-2.5 pl-4.5 text-[0.89rem] font-medium cursor-pointer list-none [&::-webkit-details-marker]:hidden before:content-[''] before:flex-none before:w-1.5 before:h-1.5 before:border-r-[1.5px] before:border-b-[1.5px] before:border-tsb15-mut before:-rotate-45 before:transition-transform before:duration-150 group-open/l2:before:rotate-45 group-has-[a[aria-current=page]]/l2:text-tsb15-acc hover:bg-tsb15-surf2 focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2 motion-reduce:before:transition-none">Onboarding</summary>
<ul class="list-none m-0 p-0">
<li><a class="flex items-center min-w-0 min-h-11 py-2 pr-2.5 pl-7.5 text-[0.88rem] text-tsb15-mut no-underline hover:bg-tsb15-surf2 focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2" href="#tsb-15-main">First week</a></li>
<li><a class="flex items-center min-w-0 min-h-11 py-2 pr-2.5 pl-7.5 text-[0.88rem] text-tsb15-mut no-underline hover:bg-tsb15-surf2 focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2" href="#tsb-15-main">Access requests</a></li>
</ul>
</details>
<details class="group/l2">
<summary class="flex items-center gap-2 min-h-11 py-2 pr-2.5 pl-4.5 text-[0.89rem] font-medium cursor-pointer list-none [&::-webkit-details-marker]:hidden before:content-[''] before:flex-none before:w-1.5 before:h-1.5 before:border-r-[1.5px] before:border-b-[1.5px] before:border-tsb15-mut before:-rotate-45 before:transition-transform before:duration-150 group-open/l2:before:rotate-45 group-has-[a[aria-current=page]]/l2:text-tsb15-acc hover:bg-tsb15-surf2 focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2 motion-reduce:before:transition-none">Reviews</summary>
<ul class="list-none m-0 p-0">
<li><a class="flex items-center min-w-0 min-h-11 py-2 pr-2.5 pl-7.5 text-[0.88rem] text-tsb15-mut no-underline hover:bg-tsb15-surf2 focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2" href="#tsb-15-main">Cycle calendar</a></li>
<li><a class="flex items-center min-w-0 min-h-11 py-2 pr-2.5 pl-7.5 text-[0.88rem] text-tsb15-mut no-underline hover:bg-tsb15-surf2 focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2" href="#tsb-15-main">Writing feedback</a></li>
</ul>
</details>
</div>
</details>
</nav>
<main class="min-w-0 content-start px-5 pt-6 pb-10 r48:px-9 r48:pt-10 r48:pb-12" id="tsb-15-main">
<p class="m-0 mb-1 text-[0.82rem] text-tsb15-mut">Operations / Deployments</p>
<h2 class="m-0 mb-4.5 font-tsb15-display text-[clamp(1.45rem,3.6vw,2rem)] leading-[1.1]">Rollback runbook</h2>
<nav class="mb-5 px-3.5 py-3 max-w-[44rem] bg-tsb15-surf2" aria-label="On this page">
<p class="m-0 mb-2 text-[0.7rem] tracking-[0.1em] uppercase text-tsb15-mut">On this page</p>
<ul class="list-none m-0 p-0 flex flex-wrap gap-1.5">
<li><a class="inline-flex items-center min-h-11 px-3.5 bg-tsb15-surf border border-tsb15-line text-[0.87rem] text-tsb15-ink no-underline hover:bg-tsb15-bg focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2 aria-[current=page]:bg-tsb15-acc aria-[current=page]:border-tsb15-acc aria-[current=page]:text-tsb15-onacc aria-[current=page]:font-semibold" href="#tsb-15-main" aria-current="page">Before you start</a></li>
<li><a class="inline-flex items-center min-h-11 px-3.5 bg-tsb15-surf border border-tsb15-line text-[0.87rem] text-tsb15-ink no-underline hover:bg-tsb15-bg focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2" href="#tsb-15-main">Pointer move</a></li>
<li><a class="inline-flex items-center min-h-11 px-3.5 bg-tsb15-surf border border-tsb15-line text-[0.87rem] text-tsb15-ink no-underline hover:bg-tsb15-bg focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2" href="#tsb-15-main">Verifying</a></li>
<li><a class="inline-flex items-center min-h-11 px-3.5 bg-tsb15-surf border border-tsb15-line text-[0.87rem] text-tsb15-ink no-underline hover:bg-tsb15-bg focus-visible:outline-2 focus-visible:outline-tsb15-acc focus-visible:-outline-offset-2" href="#tsb-15-main">Post-mortem</a></li>
</ul>
</nav>
<p class="m-0 mb-3 max-w-[40rem] text-[0.94rem] text-tsb15-mut text-pretty">Promotion is a pointer move, so a rollback is the same operation in reverse and completes in under a second. The slow part is verification — confirm the health checks on the previous build before you announce it.</p>
<p class="m-0 mb-3 max-w-[40rem] text-[0.94rem] text-tsb15-mut text-pretty">Announce in the incident channel first, then update the status page. If the previous build is more than seven days old, check the migration log before promoting it.</p>
</main>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tsb15-bg: #f7f4ee;
--color-tsb15-bg: oklch(96% 0.012 90);
--color-tsb15-surf: #fffdf8;
--color-tsb15-surf: oklch(99% 0.008 90);
--color-tsb15-surf2: #efeade;
--color-tsb15-surf2: oklch(93% 0.018 90);
--color-tsb15-line: #ddd6c6;
--color-tsb15-line: oklch(87% 0.024 90);
--color-tsb15-ink: #201d16;
--color-tsb15-ink: oklch(23% 0.014 80);
--color-tsb15-mut: #6a6252;
--color-tsb15-mut: oklch(47% 0.02 85);
--color-tsb15-acc: #4c5f2a;
--color-tsb15-acc: oklch(45% 0.1 122);
--color-tsb15-accsoft: #e4ead2;
--color-tsb15-accsoft: oklch(92% 0.03 122);
--color-tsb15-onacc: #fffdf8;
--color-tsb15-onacc: oklch(99% 0.008 90);
--font-tsb15-sans: system-ui, -apple-system, 'Segoe UI', sans-serif;
--font-tsb15-display: 'Karla', Optima, Candara, 'Gill Sans', system-ui, sans-serif;
--breakpoint-r48: 48rem;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's
w-full min-h-svh. */
.tsb-15 {
width: 100%;
min-height: 100svh;
}
.tsb-15 *,
.tsb-15 *::before,
.tsb-15 *::after {
box-sizing: border-box;
}
/* Token flip for the pure-CSS theme toggle, plus the two mandatory mirror blocks. */
.tsb-15:has(.tsb-15-theme-input:checked) {
--color-tsb15-bg: #14130f;
--color-tsb15-surf: #1b1a14;
--color-tsb15-surf2: #23211a;
--color-tsb15-line: #332f25;
--color-tsb15-ink: #f0ece1;
--color-tsb15-mut: #a49b87;
--color-tsb15-acc: #b6cf80;
--color-tsb15-acc: oklch(82% 0.12 122);
--color-tsb15-accsoft: #262d17;
--color-tsb15-onacc: #1b1a14;
}
@media (prefers-color-scheme: dark) {
.tsb-15 {
--color-tsb15-bg: #14130f;
--color-tsb15-surf: #1b1a14;
--color-tsb15-surf2: #23211a;
--color-tsb15-line: #332f25;
--color-tsb15-ink: #f0ece1;
--color-tsb15-mut: #a49b87;
--color-tsb15-acc: #b6cf80;
--color-tsb15-acc: oklch(82% 0.12 122);
--color-tsb15-accsoft: #262d17;
--color-tsb15-onacc: #1b1a14;
}
.tsb-15:has(.tsb-15-theme-input:checked) {
--color-tsb15-bg: #f7f4ee;
--color-tsb15-surf: #fffdf8;
--color-tsb15-surf2: #efeade;
--color-tsb15-line: #ddd6c6;
--color-tsb15-ink: #201d16;
--color-tsb15-mut: #6a6252;
--color-tsb15-acc: #4c5f2a;
--color-tsb15-acc: oklch(45% 0.1 122);
--color-tsb15-accsoft: #e4ead2;
--color-tsb15-onacc: #fffdf8;
}
}
@media (prefers-color-scheme: light) {
.tsb-15:has(.tsb-15-theme-input:checked) {
--color-tsb15-bg: #14130f;
--color-tsb15-surf: #1b1a14;
--color-tsb15-surf2: #23211a;
--color-tsb15-line: #332f25;
--color-tsb15-ink: #f0ece1;
--color-tsb15-mut: #a49b87;
--color-tsb15-acc: #b6cf80;
--color-tsb15-acc: oklch(82% 0.12 122);
--color-tsb15-accsoft: #262d17;
--color-tsb15-onacc: #1b1a14;
}
}
```How this works
The trap, precisely. group-open: compiles to a selector that matches if ANY ancestor with class group is open. In a nested tree that means the inner, closed group's chevron rotates as soon as its parent opens. The bug looks like a rendering glitch and is actually variant scope.
<details class="group/l1">
<summary class="before:-rotate-45 group-open/l1:before:rotate-45">Operations</summary>
<div class="border-l ml-2.5">
<details class="group/l2">
<summary class="before:-rotate-45 group-open/l2:before:rotate-45">Deployments</summary>Named groups make the scope explicit, which is exactly what nesting needs.
Why three levels is the limit. Every level adds a click before the user can see the leaf they want, and the indentation eats the rail's width. Past three, the answer is a search field and a flatter IA, not another <details>.
Ancestors highlight the path. Both the level-1 and level-2 summaries recolour when they contain the current page, using named group-has-[…] variants, so the trail to the open leaf is visible at a glance.
Still no script. Native disclosure plus :has() covers the whole thing, at any depth.
Make it yours
- Add a fourth level by adding a
group/l3— but read the gotchas first; the pattern scales technically and stops scaling for users. - Make each level exclusive with a shared
nameattribute per level; demo 16 shows the native exclusive accordion. - Change the indent by editing
ml-2.5on the branch wrapper — one place per level. - Retheme through
--color-tsb15-accand--color-tsb15-accsoft. - Swap the chevron pseudo-element for an inline SVG if you want a different glyph; the rotation variants are identical either way.
- Ship the path to the current page open, as this demo does — a knowledge base that loads fully collapsed makes the user re-navigate on every visit.
Gotchas — read before shipping
group-open:without a name is the bug this demo is built around: it matches through every open ancestor and rotates closed chevrons. Name every group in a nested tree.- Named groups must be unique per LEVEL, not per element.
group/l2on every second-level<details>is correct, because each summary only ever reads its own nearest named ancestor. before:content-['']is required before any otherbefore:utility has an effect — without it the pseudo-element does not generate.- Three levels is a design limit, not a technical one. Four is a signal to add search.
- Indentation alone does not communicate hierarchy to a screen reader; the nesting of
<details>and lists is what does, which is why the markup is not flattened. - Do not add
aria-expandedto the summaries; the browser already exposes the state.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
:has() is Baseline 2023; named group variants are a Tailwind compile-time feature with no runtime cost. Engines without :has() lose the ancestor path highlight only.