08 / 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
Tailwind Sidebar Scroll Containment with Overscroll Behavior
Two panes, two scrollbars, and the bug everyone has felt: you reach the bottom of the channel list and the page starts scrolling instead. overscroll-behavior: contain fixes it in one declaration — which Tailwind has no utility for, correctly — so this demo is the collection's clearest case for arbitrary properties, alongside the min-h-0 that makes an overflowing flex child possible at all.
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-08 block relative w-full min-h-svh bg-tsb08-bg text-tsb08-ink font-tsb08-sans leading-[1.55]" aria-label="Talon chat">
<div class="absolute top-3.5 right-3.5 z-60">
<input class="tsb-08-theme-input peer absolute inset-0 w-full h-full m-0 opacity-0 cursor-pointer" type="checkbox" id="tsb-08-theme">
<label class="grid place-items-center w-11 h-11 border-2 border-tsb08-ink bg-tsb08-surf text-tsb08-ink cursor-pointer shadow-[4px_4px_0_var(--color-tsb08-ink)] peer-focus-visible:outline-2 peer-focus-visible:outline-tsb08-acc peer-focus-visible:outline-offset-3" for="tsb-08-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="flex min-w-0 h-svh bg-tsb08-surf">
<nav class="flex-none flex flex-col w-52 min-w-0 min-h-0 overflow-y-auto bg-tsb08-surf2 border-r-2 border-tsb08-ink [overscroll-behavior:contain] [scrollbar-gutter:stable]" aria-label="Talon channels">
<p class="sticky top-0 z-2 m-0 px-2.5 py-2.5 bg-tsb08-surf2 border-b border-tsb08-line font-tsb08-display text-[0.78rem] tracking-[0.08em] uppercase text-tsb08-mut">Channels</p>
<ul class="list-none m-0 p-1 [&_a]:flex [&_a]:items-center [&_a]:min-w-0 [&_a]:min-h-11 [&_a]:px-2 [&_a]:py-1.5 [&_a]:font-tsb08-mono [&_a]:text-[0.88rem] [&_a]:text-tsb08-ink [&_a]:no-underline [&_a:hover]:bg-tsb08-accsoft [&_a:focus-visible]:outline-2 [&_a:focus-visible]:outline-tsb08-acc [&_a:focus-visible]:-outline-offset-2 [&_a[aria-current=page]]:bg-tsb08-acc [&_a[aria-current=page]]:text-tsb08-bg [&_a[aria-current=page]]:font-bold">
<li><a href="#tsb-08-thread" aria-current="page">#deployments</a></li>
<li><a href="#tsb-08-thread">#incidents</a></li>
<li><a href="#tsb-08-thread">#billing</a></li>
<li><a href="#tsb-08-thread">#design-review</a></li>
<li><a href="#tsb-08-thread">#hiring</a></li>
<li><a href="#tsb-08-thread">#audit-log</a></li>
<li><a href="#tsb-08-thread">#api-keys</a></li>
<li><a href="#tsb-08-thread">#field-notes</a></li>
<li><a href="#tsb-08-thread">#vendor-desk</a></li>
<li><a href="#tsb-08-thread">#support-escalations</a></li>
<li><a href="#tsb-08-thread">#release-notes</a></li>
<li><a href="#tsb-08-thread">#retro</a></li>
<li><a href="#tsb-08-thread">#status-page</a></li>
<li><a href="#tsb-08-thread">#certificates</a></li>
<li><a href="#tsb-08-thread">#on-call</a></li>
<li><a href="#tsb-08-thread">#runbooks</a></li>
<li><a href="#tsb-08-thread">#postmortems</a></li>
<li><a href="#tsb-08-thread">#capacity</a></li>
<li><a href="#tsb-08-thread">#data-platform</a></li>
<li><a href="#tsb-08-thread">#mobile</a></li>
<li><a href="#tsb-08-thread">#design-system</a></li>
<li><a href="#tsb-08-thread">#localisation</a></li>
<li><a href="#tsb-08-thread">#partners</a></li>
<li><a href="#tsb-08-thread">#recruiting</a></li>
<li><a href="#tsb-08-thread">#social</a></li>
<li><a href="#tsb-08-thread">#random</a></li>
</ul>
</nav>
<main class="flex-1 min-w-0 min-h-0 overflow-y-auto grid gap-3 content-start px-5 pt-4.5 pb-8 [overscroll-behavior:contain] r46:px-8 r46:pt-6.5 r46:pb-10" id="tsb-08-thread">
<p class="m-0 mb-1.5 font-tsb08-display text-[1.15rem] tracking-[0.02em] text-tsb08-acc">#deployments</p>
<p class="m-0 max-w-[44rem] text-[0.92rem] text-tsb08-mut text-pretty"><b class="block text-[0.82rem] tracking-[0.02em] text-tsb08-ink">Priya Sharma</b> Rollback finished, pointer is back on 4c19f.</p>
<p class="m-0 max-w-[44rem] text-[0.92rem] text-tsb08-mut text-pretty"><b class="block text-[0.82rem] tracking-[0.02em] text-tsb08-ink">Jordan Lee</b> Confirmed on the status page.</p>
<p class="m-0 max-w-[44rem] text-[0.92rem] text-tsb08-mut text-pretty"><b class="block text-[0.82rem] tracking-[0.02em] text-tsb08-ink">Sam Rivera</b> Retention window is 90 days now — runbook updated.</p>
<p class="m-0 max-w-[44rem] text-[0.92rem] text-tsb08-mut text-pretty"><b class="block text-[0.82rem] tracking-[0.02em] text-tsb08-ink">Ada Okafor</b> Token exchange p99 is back under 200ms.</p>
<p class="m-0 max-w-[44rem] text-[0.92rem] text-tsb08-mut text-pretty"><b class="block text-[0.82rem] tracking-[0.02em] text-tsb08-ink">Tomas Vidal</b> Staging snapshot is rebuilding, ETA 10 minutes.</p>
<p class="m-0 max-w-[44rem] text-[0.92rem] text-tsb08-mut text-pretty"><b class="block text-[0.82rem] tracking-[0.02em] text-tsb08-ink">Priya Sharma</b> Nice. I'll close the incident once staging is green.</p>
</main>
</div>
</section><section class="tsb-08 block relative w-full min-h-svh bg-tsb08-bg text-tsb08-ink font-tsb08-sans leading-[1.55]" aria-label="Talon chat">
<div class="absolute top-3.5 right-3.5 z-60">
<input class="tsb-08-theme-input peer absolute inset-0 w-full h-full m-0 opacity-0 cursor-pointer" type="checkbox" id="tsb-08-theme">
<label class="grid place-items-center w-11 h-11 border-2 border-tsb08-ink bg-tsb08-surf text-tsb08-ink cursor-pointer shadow-[4px_4px_0_var(--color-tsb08-ink)] peer-focus-visible:outline-2 peer-focus-visible:outline-tsb08-acc peer-focus-visible:outline-offset-3" for="tsb-08-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="flex min-w-0 h-svh bg-tsb08-surf">
<nav class="flex-none flex flex-col w-52 min-w-0 min-h-0 overflow-y-auto bg-tsb08-surf2 border-r-2 border-tsb08-ink [overscroll-behavior:contain] [scrollbar-gutter:stable]" aria-label="Talon channels">
<p class="sticky top-0 z-2 m-0 px-2.5 py-2.5 bg-tsb08-surf2 border-b border-tsb08-line font-tsb08-display text-[0.78rem] tracking-[0.08em] uppercase text-tsb08-mut">Channels</p>
<ul class="list-none m-0 p-1 [&_a]:flex [&_a]:items-center [&_a]:min-w-0 [&_a]:min-h-11 [&_a]:px-2 [&_a]:py-1.5 [&_a]:font-tsb08-mono [&_a]:text-[0.88rem] [&_a]:text-tsb08-ink [&_a]:no-underline [&_a:hover]:bg-tsb08-accsoft [&_a:focus-visible]:outline-2 [&_a:focus-visible]:outline-tsb08-acc [&_a:focus-visible]:-outline-offset-2 [&_a[aria-current=page]]:bg-tsb08-acc [&_a[aria-current=page]]:text-tsb08-bg [&_a[aria-current=page]]:font-bold">
<li><a href="#tsb-08-thread" aria-current="page">#deployments</a></li>
<li><a href="#tsb-08-thread">#incidents</a></li>
<li><a href="#tsb-08-thread">#billing</a></li>
<li><a href="#tsb-08-thread">#design-review</a></li>
<li><a href="#tsb-08-thread">#hiring</a></li>
<li><a href="#tsb-08-thread">#audit-log</a></li>
<li><a href="#tsb-08-thread">#api-keys</a></li>
<li><a href="#tsb-08-thread">#field-notes</a></li>
<li><a href="#tsb-08-thread">#vendor-desk</a></li>
<li><a href="#tsb-08-thread">#support-escalations</a></li>
<li><a href="#tsb-08-thread">#release-notes</a></li>
<li><a href="#tsb-08-thread">#retro</a></li>
<li><a href="#tsb-08-thread">#status-page</a></li>
<li><a href="#tsb-08-thread">#certificates</a></li>
<li><a href="#tsb-08-thread">#on-call</a></li>
<li><a href="#tsb-08-thread">#runbooks</a></li>
<li><a href="#tsb-08-thread">#postmortems</a></li>
<li><a href="#tsb-08-thread">#capacity</a></li>
<li><a href="#tsb-08-thread">#data-platform</a></li>
<li><a href="#tsb-08-thread">#mobile</a></li>
<li><a href="#tsb-08-thread">#design-system</a></li>
<li><a href="#tsb-08-thread">#localisation</a></li>
<li><a href="#tsb-08-thread">#partners</a></li>
<li><a href="#tsb-08-thread">#recruiting</a></li>
<li><a href="#tsb-08-thread">#social</a></li>
<li><a href="#tsb-08-thread">#random</a></li>
</ul>
</nav>
<main class="flex-1 min-w-0 min-h-0 overflow-y-auto grid gap-3 content-start px-5 pt-4.5 pb-8 [overscroll-behavior:contain] r46:px-8 r46:pt-6.5 r46:pb-10" id="tsb-08-thread">
<p class="m-0 mb-1.5 font-tsb08-display text-[1.15rem] tracking-[0.02em] text-tsb08-acc">#deployments</p>
<p class="m-0 max-w-[44rem] text-[0.92rem] text-tsb08-mut text-pretty"><b class="block text-[0.82rem] tracking-[0.02em] text-tsb08-ink">Priya Sharma</b> Rollback finished, pointer is back on 4c19f.</p>
<p class="m-0 max-w-[44rem] text-[0.92rem] text-tsb08-mut text-pretty"><b class="block text-[0.82rem] tracking-[0.02em] text-tsb08-ink">Jordan Lee</b> Confirmed on the status page.</p>
<p class="m-0 max-w-[44rem] text-[0.92rem] text-tsb08-mut text-pretty"><b class="block text-[0.82rem] tracking-[0.02em] text-tsb08-ink">Sam Rivera</b> Retention window is 90 days now — runbook updated.</p>
<p class="m-0 max-w-[44rem] text-[0.92rem] text-tsb08-mut text-pretty"><b class="block text-[0.82rem] tracking-[0.02em] text-tsb08-ink">Ada Okafor</b> Token exchange p99 is back under 200ms.</p>
<p class="m-0 max-w-[44rem] text-[0.92rem] text-tsb08-mut text-pretty"><b class="block text-[0.82rem] tracking-[0.02em] text-tsb08-ink">Tomas Vidal</b> Staging snapshot is rebuilding, ETA 10 minutes.</p>
<p class="m-0 max-w-[44rem] text-[0.92rem] text-tsb08-mut text-pretty"><b class="block text-[0.82rem] tracking-[0.02em] text-tsb08-ink">Priya Sharma</b> Nice. I'll close the incident once staging is green.</p>
</main>
</div>
</section>@import "tailwindcss";
@theme {
--color-tsb08-bg: #211d19;
--color-tsb08-bg: oklch(24% 0.012 60);
--color-tsb08-surf: #2b2521;
--color-tsb08-surf: oklch(29% 0.014 60);
--color-tsb08-surf2: #362e28;
--color-tsb08-surf2: oklch(34% 0.016 60);
--color-tsb08-line: #453b33;
--color-tsb08-line: oklch(40% 0.018 60);
--color-tsb08-ink: #f4ece3;
--color-tsb08-ink: oklch(95% 0.014 80);
--color-tsb08-mut: #b3a496;
--color-tsb08-mut: oklch(73% 0.02 75);
--color-tsb08-acc: #e0a13c;
--color-tsb08-acc: oklch(76% 0.14 72);
--color-tsb08-accsoft: #3d3021;
--color-tsb08-accsoft: oklch(33% 0.03 70);
--font-tsb08-sans: system-ui, -apple-system, 'Segoe UI', sans-serif;
--font-tsb08-display: 'Roboto Slab', Rockwell, 'Bookman Old Style', Georgia, serif;
--font-tsb08-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
--breakpoint-r46: 46rem;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's
w-full min-h-svh. */
.tsb-08 {
width: 100%;
min-height: 100svh;
}
.tsb-08 *,
.tsb-08 *::before,
.tsb-08 *::after {
box-sizing: border-box;
}
/* This demo is dark by default, so the flip is inverted: the @theme block above IS the
dark palette, and the light mirror lives in prefers-color-scheme: light. */
.tsb-08:has(.tsb-08-theme-input:checked) {
--color-tsb08-bg: #f7f2ea;
--color-tsb08-surf: #ffffff;
--color-tsb08-surf2: #efe7dc;
--color-tsb08-line: #ddd0c0;
--color-tsb08-ink: #241d17;
--color-tsb08-mut: #6c5c4e;
--color-tsb08-acc: #8a5210;
--color-tsb08-acc: oklch(48% 0.11 68);
--color-tsb08-accsoft: #f2e3cf;
}
@media (prefers-color-scheme: light) {
.tsb-08 {
--color-tsb08-bg: #f7f2ea;
--color-tsb08-surf: #ffffff;
--color-tsb08-surf2: #efe7dc;
--color-tsb08-line: #ddd0c0;
--color-tsb08-ink: #241d17;
--color-tsb08-mut: #6c5c4e;
--color-tsb08-acc: #8a5210;
--color-tsb08-acc: oklch(48% 0.11 68);
--color-tsb08-accsoft: #f2e3cf;
}
.tsb-08:has(.tsb-08-theme-input:checked) {
--color-tsb08-bg: #211d19;
--color-tsb08-surf: #2b2521;
--color-tsb08-surf2: #362e28;
--color-tsb08-line: #453b33;
--color-tsb08-ink: #f4ece3;
--color-tsb08-mut: #b3a496;
--color-tsb08-acc: #e0a13c;
--color-tsb08-acc: oklch(76% 0.14 72);
--color-tsb08-accsoft: #3d3021;
}
}
@media (prefers-color-scheme: dark) {
.tsb-08:has(.tsb-08-theme-input:checked) {
--color-tsb08-bg: #f7f2ea;
--color-tsb08-surf: #ffffff;
--color-tsb08-surf2: #efe7dc;
--color-tsb08-line: #ddd0c0;
--color-tsb08-ink: #241d17;
--color-tsb08-mut: #6c5c4e;
--color-tsb08-acc: #8a5210;
--color-tsb08-acc: oklch(48% 0.11 68);
--color-tsb08-accsoft: #f2e3cf;
}
}@import "tailwindcss";
@theme {
--color-tsb08-bg: #211d19;
--color-tsb08-bg: oklch(24% 0.012 60);
--color-tsb08-surf: #2b2521;
--color-tsb08-surf: oklch(29% 0.014 60);
--color-tsb08-surf2: #362e28;
--color-tsb08-surf2: oklch(34% 0.016 60);
--color-tsb08-line: #453b33;
--color-tsb08-line: oklch(40% 0.018 60);
--color-tsb08-ink: #f4ece3;
--color-tsb08-ink: oklch(95% 0.014 80);
--color-tsb08-mut: #b3a496;
--color-tsb08-mut: oklch(73% 0.02 75);
--color-tsb08-acc: #e0a13c;
--color-tsb08-acc: oklch(76% 0.14 72);
--color-tsb08-accsoft: #3d3021;
--color-tsb08-accsoft: oklch(33% 0.03 70);
--font-tsb08-sans: system-ui, -apple-system, 'Segoe UI', sans-serif;
--font-tsb08-display: 'Roboto Slab', Rockwell, 'Bookman Old Style', Georgia, serif;
--font-tsb08-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
--breakpoint-r46: 46rem;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's
w-full min-h-svh. */
.tsb-08 {
width: 100%;
min-height: 100svh;
}
.tsb-08 *,
.tsb-08 *::before,
.tsb-08 *::after {
box-sizing: border-box;
}
/* This demo is dark by default, so the flip is inverted: the @theme block above IS the
dark palette, and the light mirror lives in prefers-color-scheme: light. */
.tsb-08:has(.tsb-08-theme-input:checked) {
--color-tsb08-bg: #f7f2ea;
--color-tsb08-surf: #ffffff;
--color-tsb08-surf2: #efe7dc;
--color-tsb08-line: #ddd0c0;
--color-tsb08-ink: #241d17;
--color-tsb08-mut: #6c5c4e;
--color-tsb08-acc: #8a5210;
--color-tsb08-acc: oklch(48% 0.11 68);
--color-tsb08-accsoft: #f2e3cf;
}
@media (prefers-color-scheme: light) {
.tsb-08 {
--color-tsb08-bg: #f7f2ea;
--color-tsb08-surf: #ffffff;
--color-tsb08-surf2: #efe7dc;
--color-tsb08-line: #ddd0c0;
--color-tsb08-ink: #241d17;
--color-tsb08-mut: #6c5c4e;
--color-tsb08-acc: #8a5210;
--color-tsb08-acc: oklch(48% 0.11 68);
--color-tsb08-accsoft: #f2e3cf;
}
.tsb-08:has(.tsb-08-theme-input:checked) {
--color-tsb08-bg: #211d19;
--color-tsb08-surf: #2b2521;
--color-tsb08-surf2: #362e28;
--color-tsb08-line: #453b33;
--color-tsb08-ink: #f4ece3;
--color-tsb08-mut: #b3a496;
--color-tsb08-acc: #e0a13c;
--color-tsb08-acc: oklch(76% 0.14 72);
--color-tsb08-accsoft: #3d3021;
}
}
@media (prefers-color-scheme: dark) {
.tsb-08:has(.tsb-08-theme-input:checked) {
--color-tsb08-bg: #f7f2ea;
--color-tsb08-surf: #ffffff;
--color-tsb08-surf2: #efe7dc;
--color-tsb08-line: #ddd0c0;
--color-tsb08-ink: #241d17;
--color-tsb08-mut: #6c5c4e;
--color-tsb08-acc: #8a5210;
--color-tsb08-acc: oklch(48% 0.11 68);
--color-tsb08-accsoft: #f2e3cf;
}
}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-tsb08-bg: #211d19;
--color-tsb08-bg: oklch(24% 0.012 60);
--color-tsb08-surf: #2b2521;
--color-tsb08-surf: oklch(29% 0.014 60);
--color-tsb08-surf2: #362e28;
--color-tsb08-surf2: oklch(34% 0.016 60);
--color-tsb08-line: #453b33;
--color-tsb08-line: oklch(40% 0.018 60);
--color-tsb08-ink: #f4ece3;
--color-tsb08-ink: oklch(95% 0.014 80);
--color-tsb08-mut: #b3a496;
--color-tsb08-mut: oklch(73% 0.02 75);
--color-tsb08-acc: #e0a13c;
--color-tsb08-acc: oklch(76% 0.14 72);
--color-tsb08-accsoft: #3d3021;
--color-tsb08-accsoft: oklch(33% 0.03 70);
--font-tsb08-sans: system-ui, -apple-system, 'Segoe UI', sans-serif;
--font-tsb08-display: 'Roboto Slab', Rockwell, 'Bookman Old Style', Georgia, serif;
--font-tsb08-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
--breakpoint-r46: 46rem;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's
w-full min-h-svh. */
.tsb-08 {
width: 100%;
min-height: 100svh;
}
.tsb-08 *,
.tsb-08 *::before,
.tsb-08 *::after {
box-sizing: border-box;
}
/* This demo is dark by default, so the flip is inverted: the @theme block above IS the
dark palette, and the light mirror lives in prefers-color-scheme: light. */
.tsb-08:has(.tsb-08-theme-input:checked) {
--color-tsb08-bg: #f7f2ea;
--color-tsb08-surf: #ffffff;
--color-tsb08-surf2: #efe7dc;
--color-tsb08-line: #ddd0c0;
--color-tsb08-ink: #241d17;
--color-tsb08-mut: #6c5c4e;
--color-tsb08-acc: #8a5210;
--color-tsb08-acc: oklch(48% 0.11 68);
--color-tsb08-accsoft: #f2e3cf;
}
@media (prefers-color-scheme: light) {
.tsb-08 {
--color-tsb08-bg: #f7f2ea;
--color-tsb08-surf: #ffffff;
--color-tsb08-surf2: #efe7dc;
--color-tsb08-line: #ddd0c0;
--color-tsb08-ink: #241d17;
--color-tsb08-mut: #6c5c4e;
--color-tsb08-acc: #8a5210;
--color-tsb08-acc: oklch(48% 0.11 68);
--color-tsb08-accsoft: #f2e3cf;
}
.tsb-08:has(.tsb-08-theme-input:checked) {
--color-tsb08-bg: #211d19;
--color-tsb08-surf: #2b2521;
--color-tsb08-surf2: #362e28;
--color-tsb08-line: #453b33;
--color-tsb08-ink: #f4ece3;
--color-tsb08-mut: #b3a496;
--color-tsb08-acc: #e0a13c;
--color-tsb08-acc: oklch(76% 0.14 72);
--color-tsb08-accsoft: #3d3021;
}
}
@media (prefers-color-scheme: dark) {
.tsb-08:has(.tsb-08-theme-input:checked) {
--color-tsb08-bg: #f7f2ea;
--color-tsb08-surf: #ffffff;
--color-tsb08-surf2: #efe7dc;
--color-tsb08-line: #ddd0c0;
--color-tsb08-ink: #241d17;
--color-tsb08-mut: #6c5c4e;
--color-tsb08-acc: #8a5210;
--color-tsb08-acc: oklch(48% 0.11 68);
--color-tsb08-accsoft: #f2e3cf;
}
}@import "tailwindcss";
@theme {
--color-tsb08-bg: #211d19;
--color-tsb08-bg: oklch(24% 0.012 60);
--color-tsb08-surf: #2b2521;
--color-tsb08-surf: oklch(29% 0.014 60);
--color-tsb08-surf2: #362e28;
--color-tsb08-surf2: oklch(34% 0.016 60);
--color-tsb08-line: #453b33;
--color-tsb08-line: oklch(40% 0.018 60);
--color-tsb08-ink: #f4ece3;
--color-tsb08-ink: oklch(95% 0.014 80);
--color-tsb08-mut: #b3a496;
--color-tsb08-mut: oklch(73% 0.02 75);
--color-tsb08-acc: #e0a13c;
--color-tsb08-acc: oklch(76% 0.14 72);
--color-tsb08-accsoft: #3d3021;
--color-tsb08-accsoft: oklch(33% 0.03 70);
--font-tsb08-sans: system-ui, -apple-system, 'Segoe UI', sans-serif;
--font-tsb08-display: 'Roboto Slab', Rockwell, 'Bookman Old Style', Georgia, serif;
--font-tsb08-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
--breakpoint-r46: 46rem;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's
w-full min-h-svh. */
.tsb-08 {
width: 100%;
min-height: 100svh;
}
.tsb-08 *,
.tsb-08 *::before,
.tsb-08 *::after {
box-sizing: border-box;
}
/* This demo is dark by default, so the flip is inverted: the @theme block above IS the
dark palette, and the light mirror lives in prefers-color-scheme: light. */
.tsb-08:has(.tsb-08-theme-input:checked) {
--color-tsb08-bg: #f7f2ea;
--color-tsb08-surf: #ffffff;
--color-tsb08-surf2: #efe7dc;
--color-tsb08-line: #ddd0c0;
--color-tsb08-ink: #241d17;
--color-tsb08-mut: #6c5c4e;
--color-tsb08-acc: #8a5210;
--color-tsb08-acc: oklch(48% 0.11 68);
--color-tsb08-accsoft: #f2e3cf;
}
@media (prefers-color-scheme: light) {
.tsb-08 {
--color-tsb08-bg: #f7f2ea;
--color-tsb08-surf: #ffffff;
--color-tsb08-surf2: #efe7dc;
--color-tsb08-line: #ddd0c0;
--color-tsb08-ink: #241d17;
--color-tsb08-mut: #6c5c4e;
--color-tsb08-acc: #8a5210;
--color-tsb08-acc: oklch(48% 0.11 68);
--color-tsb08-accsoft: #f2e3cf;
}
.tsb-08:has(.tsb-08-theme-input:checked) {
--color-tsb08-bg: #211d19;
--color-tsb08-surf: #2b2521;
--color-tsb08-surf2: #362e28;
--color-tsb08-line: #453b33;
--color-tsb08-ink: #f4ece3;
--color-tsb08-mut: #b3a496;
--color-tsb08-acc: #e0a13c;
--color-tsb08-acc: oklch(76% 0.14 72);
--color-tsb08-accsoft: #3d3021;
}
}
@media (prefers-color-scheme: dark) {
.tsb-08:has(.tsb-08-theme-input:checked) {
--color-tsb08-bg: #f7f2ea;
--color-tsb08-surf: #ffffff;
--color-tsb08-surf2: #efe7dc;
--color-tsb08-line: #ddd0c0;
--color-tsb08-ink: #241d17;
--color-tsb08-mut: #6c5c4e;
--color-tsb08-acc: #8a5210;
--color-tsb08-acc: oklch(48% 0.11 68);
--color-tsb08-accsoft: #f2e3cf;
}
}<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=Roboto+Slab:wght@400;600;700&family=JetBrains+Mono:wght@400;500;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=Roboto+Slab:wght@400;600;700&family=JetBrains+Mono:wght@400;500;700&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
overscroll-behaviorandscrollbar-gutterare the collection's best argument for arbitrary properties: single correct structural answers, not design-scale values, and clearer as[overscroll-behavior:contain]than as an invented class name.- Twenty-six identical links would be twenty-six identical class lists, so this demo uses descendant arbitrary variants on the
<ul>instead —[&_a]:,[&_a:hover]:,[&_a[aria-current=page]]:. That is the honest port of a.list arule, and the one place in the collection where utilities on the parent beat utilities on the child. min-h-0is the least obvious utility in the whole set. It is what the CSS version wrote asmin-block-size: 0, and it is the reason the panes scroll rather than the page.- The sticky section title keeps
bg-tsb08-surf2andz-2in the class list, right next tosticky top-0— in CSS those three lines are easy to separate and then lose. - The hard-offset shadow ports as
shadow-[4px_4px_0_var(--color-tsb08-ink)], reading the same ink token as the 2px borders. - The dark-first token flip stays in the stylesheet, with the light palette in the
prefers-color-scheme: lightblock — the mirror structure is the same as the rest of the collection, just inverted.
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 Sidebar Scroll Containment with Overscroll Behavior
Source: https://codefronts.com/navigation/tailwind-css-sidebars/tailwind-sidebar-scroll-containment-with-overscroll-behavior/
Two panes, two scrollbars, and the bug everyone has felt: you reach the bottom of the channel list and the page starts scrolling instead. overscroll-behavior: contain fixes it in one declaration — which Tailwind has no utility for, correctly — so this demo is the collection's clearest case for arbitrary properties, alongside the min-h-0 that makes an overflowing flex child possible at all.
## HTML
```html
<section class="tsb-08 block relative w-full min-h-svh bg-tsb08-bg text-tsb08-ink font-tsb08-sans leading-[1.55]" aria-label="Talon chat">
<div class="absolute top-3.5 right-3.5 z-60">
<input class="tsb-08-theme-input peer absolute inset-0 w-full h-full m-0 opacity-0 cursor-pointer" type="checkbox" id="tsb-08-theme">
<label class="grid place-items-center w-11 h-11 border-2 border-tsb08-ink bg-tsb08-surf text-tsb08-ink cursor-pointer shadow-[4px_4px_0_var(--color-tsb08-ink)] peer-focus-visible:outline-2 peer-focus-visible:outline-tsb08-acc peer-focus-visible:outline-offset-3" for="tsb-08-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="flex min-w-0 h-svh bg-tsb08-surf">
<nav class="flex-none flex flex-col w-52 min-w-0 min-h-0 overflow-y-auto bg-tsb08-surf2 border-r-2 border-tsb08-ink [overscroll-behavior:contain] [scrollbar-gutter:stable]" aria-label="Talon channels">
<p class="sticky top-0 z-2 m-0 px-2.5 py-2.5 bg-tsb08-surf2 border-b border-tsb08-line font-tsb08-display text-[0.78rem] tracking-[0.08em] uppercase text-tsb08-mut">Channels</p>
<ul class="list-none m-0 p-1 [&_a]:flex [&_a]:items-center [&_a]:min-w-0 [&_a]:min-h-11 [&_a]:px-2 [&_a]:py-1.5 [&_a]:font-tsb08-mono [&_a]:text-[0.88rem] [&_a]:text-tsb08-ink [&_a]:no-underline [&_a:hover]:bg-tsb08-accsoft [&_a:focus-visible]:outline-2 [&_a:focus-visible]:outline-tsb08-acc [&_a:focus-visible]:-outline-offset-2 [&_a[aria-current=page]]:bg-tsb08-acc [&_a[aria-current=page]]:text-tsb08-bg [&_a[aria-current=page]]:font-bold">
<li><a href="#tsb-08-thread" aria-current="page">#deployments</a></li>
<li><a href="#tsb-08-thread">#incidents</a></li>
<li><a href="#tsb-08-thread">#billing</a></li>
<li><a href="#tsb-08-thread">#design-review</a></li>
<li><a href="#tsb-08-thread">#hiring</a></li>
<li><a href="#tsb-08-thread">#audit-log</a></li>
<li><a href="#tsb-08-thread">#api-keys</a></li>
<li><a href="#tsb-08-thread">#field-notes</a></li>
<li><a href="#tsb-08-thread">#vendor-desk</a></li>
<li><a href="#tsb-08-thread">#support-escalations</a></li>
<li><a href="#tsb-08-thread">#release-notes</a></li>
<li><a href="#tsb-08-thread">#retro</a></li>
<li><a href="#tsb-08-thread">#status-page</a></li>
<li><a href="#tsb-08-thread">#certificates</a></li>
<li><a href="#tsb-08-thread">#on-call</a></li>
<li><a href="#tsb-08-thread">#runbooks</a></li>
<li><a href="#tsb-08-thread">#postmortems</a></li>
<li><a href="#tsb-08-thread">#capacity</a></li>
<li><a href="#tsb-08-thread">#data-platform</a></li>
<li><a href="#tsb-08-thread">#mobile</a></li>
<li><a href="#tsb-08-thread">#design-system</a></li>
<li><a href="#tsb-08-thread">#localisation</a></li>
<li><a href="#tsb-08-thread">#partners</a></li>
<li><a href="#tsb-08-thread">#recruiting</a></li>
<li><a href="#tsb-08-thread">#social</a></li>
<li><a href="#tsb-08-thread">#random</a></li>
</ul>
</nav>
<main class="flex-1 min-w-0 min-h-0 overflow-y-auto grid gap-3 content-start px-5 pt-4.5 pb-8 [overscroll-behavior:contain] r46:px-8 r46:pt-6.5 r46:pb-10" id="tsb-08-thread">
<p class="m-0 mb-1.5 font-tsb08-display text-[1.15rem] tracking-[0.02em] text-tsb08-acc">#deployments</p>
<p class="m-0 max-w-[44rem] text-[0.92rem] text-tsb08-mut text-pretty"><b class="block text-[0.82rem] tracking-[0.02em] text-tsb08-ink">Priya Sharma</b> Rollback finished, pointer is back on 4c19f.</p>
<p class="m-0 max-w-[44rem] text-[0.92rem] text-tsb08-mut text-pretty"><b class="block text-[0.82rem] tracking-[0.02em] text-tsb08-ink">Jordan Lee</b> Confirmed on the status page.</p>
<p class="m-0 max-w-[44rem] text-[0.92rem] text-tsb08-mut text-pretty"><b class="block text-[0.82rem] tracking-[0.02em] text-tsb08-ink">Sam Rivera</b> Retention window is 90 days now — runbook updated.</p>
<p class="m-0 max-w-[44rem] text-[0.92rem] text-tsb08-mut text-pretty"><b class="block text-[0.82rem] tracking-[0.02em] text-tsb08-ink">Ada Okafor</b> Token exchange p99 is back under 200ms.</p>
<p class="m-0 max-w-[44rem] text-[0.92rem] text-tsb08-mut text-pretty"><b class="block text-[0.82rem] tracking-[0.02em] text-tsb08-ink">Tomas Vidal</b> Staging snapshot is rebuilding, ETA 10 minutes.</p>
<p class="m-0 max-w-[44rem] text-[0.92rem] text-tsb08-mut text-pretty"><b class="block text-[0.82rem] tracking-[0.02em] text-tsb08-ink">Priya Sharma</b> Nice. I'll close the incident once staging is green.</p>
</main>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tsb08-bg: #211d19;
--color-tsb08-bg: oklch(24% 0.012 60);
--color-tsb08-surf: #2b2521;
--color-tsb08-surf: oklch(29% 0.014 60);
--color-tsb08-surf2: #362e28;
--color-tsb08-surf2: oklch(34% 0.016 60);
--color-tsb08-line: #453b33;
--color-tsb08-line: oklch(40% 0.018 60);
--color-tsb08-ink: #f4ece3;
--color-tsb08-ink: oklch(95% 0.014 80);
--color-tsb08-mut: #b3a496;
--color-tsb08-mut: oklch(73% 0.02 75);
--color-tsb08-acc: #e0a13c;
--color-tsb08-acc: oklch(76% 0.14 72);
--color-tsb08-accsoft: #3d3021;
--color-tsb08-accsoft: oklch(33% 0.03 70);
--font-tsb08-sans: system-ui, -apple-system, 'Segoe UI', sans-serif;
--font-tsb08-display: 'Roboto Slab', Rockwell, 'Bookman Old Style', Georgia, serif;
--font-tsb08-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
--breakpoint-r46: 46rem;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's
w-full min-h-svh. */
.tsb-08 {
width: 100%;
min-height: 100svh;
}
.tsb-08 *,
.tsb-08 *::before,
.tsb-08 *::after {
box-sizing: border-box;
}
/* This demo is dark by default, so the flip is inverted: the @theme block above IS the
dark palette, and the light mirror lives in prefers-color-scheme: light. */
.tsb-08:has(.tsb-08-theme-input:checked) {
--color-tsb08-bg: #f7f2ea;
--color-tsb08-surf: #ffffff;
--color-tsb08-surf2: #efe7dc;
--color-tsb08-line: #ddd0c0;
--color-tsb08-ink: #241d17;
--color-tsb08-mut: #6c5c4e;
--color-tsb08-acc: #8a5210;
--color-tsb08-acc: oklch(48% 0.11 68);
--color-tsb08-accsoft: #f2e3cf;
}
@media (prefers-color-scheme: light) {
.tsb-08 {
--color-tsb08-bg: #f7f2ea;
--color-tsb08-surf: #ffffff;
--color-tsb08-surf2: #efe7dc;
--color-tsb08-line: #ddd0c0;
--color-tsb08-ink: #241d17;
--color-tsb08-mut: #6c5c4e;
--color-tsb08-acc: #8a5210;
--color-tsb08-acc: oklch(48% 0.11 68);
--color-tsb08-accsoft: #f2e3cf;
}
.tsb-08:has(.tsb-08-theme-input:checked) {
--color-tsb08-bg: #211d19;
--color-tsb08-surf: #2b2521;
--color-tsb08-surf2: #362e28;
--color-tsb08-line: #453b33;
--color-tsb08-ink: #f4ece3;
--color-tsb08-mut: #b3a496;
--color-tsb08-acc: #e0a13c;
--color-tsb08-acc: oklch(76% 0.14 72);
--color-tsb08-accsoft: #3d3021;
}
}
@media (prefers-color-scheme: dark) {
.tsb-08:has(.tsb-08-theme-input:checked) {
--color-tsb08-bg: #f7f2ea;
--color-tsb08-surf: #ffffff;
--color-tsb08-surf2: #efe7dc;
--color-tsb08-line: #ddd0c0;
--color-tsb08-ink: #241d17;
--color-tsb08-mut: #6c5c4e;
--color-tsb08-acc: #8a5210;
--color-tsb08-acc: oklch(48% 0.11 68);
--color-tsb08-accsoft: #f2e3cf;
}
}
```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 Sidebar Scroll Containment with Overscroll Behavior
Source: https://codefronts.com/navigation/tailwind-css-sidebars/tailwind-sidebar-scroll-containment-with-overscroll-behavior/
Two panes, two scrollbars, and the bug everyone has felt: you reach the bottom of the channel list and the page starts scrolling instead. overscroll-behavior: contain fixes it in one declaration — which Tailwind has no utility for, correctly — so this demo is the collection's clearest case for arbitrary properties, alongside the min-h-0 that makes an overflowing flex child possible at all.
## HTML
```html
<section class="tsb-08 block relative w-full min-h-svh bg-tsb08-bg text-tsb08-ink font-tsb08-sans leading-[1.55]" aria-label="Talon chat">
<div class="absolute top-3.5 right-3.5 z-60">
<input class="tsb-08-theme-input peer absolute inset-0 w-full h-full m-0 opacity-0 cursor-pointer" type="checkbox" id="tsb-08-theme">
<label class="grid place-items-center w-11 h-11 border-2 border-tsb08-ink bg-tsb08-surf text-tsb08-ink cursor-pointer shadow-[4px_4px_0_var(--color-tsb08-ink)] peer-focus-visible:outline-2 peer-focus-visible:outline-tsb08-acc peer-focus-visible:outline-offset-3" for="tsb-08-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="flex min-w-0 h-svh bg-tsb08-surf">
<nav class="flex-none flex flex-col w-52 min-w-0 min-h-0 overflow-y-auto bg-tsb08-surf2 border-r-2 border-tsb08-ink [overscroll-behavior:contain] [scrollbar-gutter:stable]" aria-label="Talon channels">
<p class="sticky top-0 z-2 m-0 px-2.5 py-2.5 bg-tsb08-surf2 border-b border-tsb08-line font-tsb08-display text-[0.78rem] tracking-[0.08em] uppercase text-tsb08-mut">Channels</p>
<ul class="list-none m-0 p-1 [&_a]:flex [&_a]:items-center [&_a]:min-w-0 [&_a]:min-h-11 [&_a]:px-2 [&_a]:py-1.5 [&_a]:font-tsb08-mono [&_a]:text-[0.88rem] [&_a]:text-tsb08-ink [&_a]:no-underline [&_a:hover]:bg-tsb08-accsoft [&_a:focus-visible]:outline-2 [&_a:focus-visible]:outline-tsb08-acc [&_a:focus-visible]:-outline-offset-2 [&_a[aria-current=page]]:bg-tsb08-acc [&_a[aria-current=page]]:text-tsb08-bg [&_a[aria-current=page]]:font-bold">
<li><a href="#tsb-08-thread" aria-current="page">#deployments</a></li>
<li><a href="#tsb-08-thread">#incidents</a></li>
<li><a href="#tsb-08-thread">#billing</a></li>
<li><a href="#tsb-08-thread">#design-review</a></li>
<li><a href="#tsb-08-thread">#hiring</a></li>
<li><a href="#tsb-08-thread">#audit-log</a></li>
<li><a href="#tsb-08-thread">#api-keys</a></li>
<li><a href="#tsb-08-thread">#field-notes</a></li>
<li><a href="#tsb-08-thread">#vendor-desk</a></li>
<li><a href="#tsb-08-thread">#support-escalations</a></li>
<li><a href="#tsb-08-thread">#release-notes</a></li>
<li><a href="#tsb-08-thread">#retro</a></li>
<li><a href="#tsb-08-thread">#status-page</a></li>
<li><a href="#tsb-08-thread">#certificates</a></li>
<li><a href="#tsb-08-thread">#on-call</a></li>
<li><a href="#tsb-08-thread">#runbooks</a></li>
<li><a href="#tsb-08-thread">#postmortems</a></li>
<li><a href="#tsb-08-thread">#capacity</a></li>
<li><a href="#tsb-08-thread">#data-platform</a></li>
<li><a href="#tsb-08-thread">#mobile</a></li>
<li><a href="#tsb-08-thread">#design-system</a></li>
<li><a href="#tsb-08-thread">#localisation</a></li>
<li><a href="#tsb-08-thread">#partners</a></li>
<li><a href="#tsb-08-thread">#recruiting</a></li>
<li><a href="#tsb-08-thread">#social</a></li>
<li><a href="#tsb-08-thread">#random</a></li>
</ul>
</nav>
<main class="flex-1 min-w-0 min-h-0 overflow-y-auto grid gap-3 content-start px-5 pt-4.5 pb-8 [overscroll-behavior:contain] r46:px-8 r46:pt-6.5 r46:pb-10" id="tsb-08-thread">
<p class="m-0 mb-1.5 font-tsb08-display text-[1.15rem] tracking-[0.02em] text-tsb08-acc">#deployments</p>
<p class="m-0 max-w-[44rem] text-[0.92rem] text-tsb08-mut text-pretty"><b class="block text-[0.82rem] tracking-[0.02em] text-tsb08-ink">Priya Sharma</b> Rollback finished, pointer is back on 4c19f.</p>
<p class="m-0 max-w-[44rem] text-[0.92rem] text-tsb08-mut text-pretty"><b class="block text-[0.82rem] tracking-[0.02em] text-tsb08-ink">Jordan Lee</b> Confirmed on the status page.</p>
<p class="m-0 max-w-[44rem] text-[0.92rem] text-tsb08-mut text-pretty"><b class="block text-[0.82rem] tracking-[0.02em] text-tsb08-ink">Sam Rivera</b> Retention window is 90 days now — runbook updated.</p>
<p class="m-0 max-w-[44rem] text-[0.92rem] text-tsb08-mut text-pretty"><b class="block text-[0.82rem] tracking-[0.02em] text-tsb08-ink">Ada Okafor</b> Token exchange p99 is back under 200ms.</p>
<p class="m-0 max-w-[44rem] text-[0.92rem] text-tsb08-mut text-pretty"><b class="block text-[0.82rem] tracking-[0.02em] text-tsb08-ink">Tomas Vidal</b> Staging snapshot is rebuilding, ETA 10 minutes.</p>
<p class="m-0 max-w-[44rem] text-[0.92rem] text-tsb08-mut text-pretty"><b class="block text-[0.82rem] tracking-[0.02em] text-tsb08-ink">Priya Sharma</b> Nice. I'll close the incident once staging is green.</p>
</main>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tsb08-bg: #211d19;
--color-tsb08-bg: oklch(24% 0.012 60);
--color-tsb08-surf: #2b2521;
--color-tsb08-surf: oklch(29% 0.014 60);
--color-tsb08-surf2: #362e28;
--color-tsb08-surf2: oklch(34% 0.016 60);
--color-tsb08-line: #453b33;
--color-tsb08-line: oklch(40% 0.018 60);
--color-tsb08-ink: #f4ece3;
--color-tsb08-ink: oklch(95% 0.014 80);
--color-tsb08-mut: #b3a496;
--color-tsb08-mut: oklch(73% 0.02 75);
--color-tsb08-acc: #e0a13c;
--color-tsb08-acc: oklch(76% 0.14 72);
--color-tsb08-accsoft: #3d3021;
--color-tsb08-accsoft: oklch(33% 0.03 70);
--font-tsb08-sans: system-ui, -apple-system, 'Segoe UI', sans-serif;
--font-tsb08-display: 'Roboto Slab', Rockwell, 'Bookman Old Style', Georgia, serif;
--font-tsb08-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
--breakpoint-r46: 46rem;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's
w-full min-h-svh. */
.tsb-08 {
width: 100%;
min-height: 100svh;
}
.tsb-08 *,
.tsb-08 *::before,
.tsb-08 *::after {
box-sizing: border-box;
}
/* This demo is dark by default, so the flip is inverted: the @theme block above IS the
dark palette, and the light mirror lives in prefers-color-scheme: light. */
.tsb-08:has(.tsb-08-theme-input:checked) {
--color-tsb08-bg: #f7f2ea;
--color-tsb08-surf: #ffffff;
--color-tsb08-surf2: #efe7dc;
--color-tsb08-line: #ddd0c0;
--color-tsb08-ink: #241d17;
--color-tsb08-mut: #6c5c4e;
--color-tsb08-acc: #8a5210;
--color-tsb08-acc: oklch(48% 0.11 68);
--color-tsb08-accsoft: #f2e3cf;
}
@media (prefers-color-scheme: light) {
.tsb-08 {
--color-tsb08-bg: #f7f2ea;
--color-tsb08-surf: #ffffff;
--color-tsb08-surf2: #efe7dc;
--color-tsb08-line: #ddd0c0;
--color-tsb08-ink: #241d17;
--color-tsb08-mut: #6c5c4e;
--color-tsb08-acc: #8a5210;
--color-tsb08-acc: oklch(48% 0.11 68);
--color-tsb08-accsoft: #f2e3cf;
}
.tsb-08:has(.tsb-08-theme-input:checked) {
--color-tsb08-bg: #211d19;
--color-tsb08-surf: #2b2521;
--color-tsb08-surf2: #362e28;
--color-tsb08-line: #453b33;
--color-tsb08-ink: #f4ece3;
--color-tsb08-mut: #b3a496;
--color-tsb08-acc: #e0a13c;
--color-tsb08-acc: oklch(76% 0.14 72);
--color-tsb08-accsoft: #3d3021;
}
}
@media (prefers-color-scheme: dark) {
.tsb-08:has(.tsb-08-theme-input:checked) {
--color-tsb08-bg: #f7f2ea;
--color-tsb08-surf: #ffffff;
--color-tsb08-surf2: #efe7dc;
--color-tsb08-line: #ddd0c0;
--color-tsb08-ink: #241d17;
--color-tsb08-mut: #6c5c4e;
--color-tsb08-acc: #8a5210;
--color-tsb08-acc: oklch(48% 0.11 68);
--color-tsb08-accsoft: #f2e3cf;
}
}
```How this works
Three utilities make an independent scroll pane, and two of them are easy to forget. overflow-y-auto is the obvious one. min-h-0 is what lets a flex child shrink below its content height — without it the rail grows to fit twenty-six channels and the whole page scrolls instead. [overscroll-behavior:contain] is what stops a flick that reaches the end of the list from continuing into the page behind.
<nav class="flex-none w-52 min-h-0 overflow-y-auto
[overscroll-behavior:contain]
[scrollbar-gutter:stable]">Why Tailwind should not ship these as utilities. A utility scale earns its place when the values are a design vocabulary — spacing, colour, radius. contain and stable are single correct answers to structural questions, and an arbitrary property says so more clearly than a memorised class name would.
The sticky title is sticky to the pane, not the page. Because the rail is the scroll container, sticky top-0 pins the “Channels” heading to the top of the list. It needs an opaque background and a z- above the links, or the items scroll through it.
This demo is dark by default. The token flip is inverted relative to the rest of the collection: the base palette is the dark one and the prefers-color-scheme: light block provides the light mirror.
Make it yours
- Swap
[overscroll-behavior:contain]fornoneto also kill pull-to-refresh and the rubber-band effect on the pane. - Rail width is the single
w-52on the nav. - Drop the sticky title by deleting
sticky top-0 z-2; the containment is independent of it. - Retheme through
--color-tsb08-accand--color-tsb08-surf2; the demo is deliberately dark-first, and the light mirror lives in the stylesheet. - Soften the brutalist edges by replacing
border-2and the hard shadow withborderandshadow-sm. - For a page-level version of this, put the containment on the
<body>scroller instead; the pattern is identical.
Gotchas — read before shipping
- Without
min-h-0the flex child refuses to shrink andoverflow-y-autonever engages. It is the most common reason “my sidebar scrolls the page”. h-screenon the app shell is doing real work here: a scroll pane needs a bounded height, andmin-h-svhalone does not bound it.- A sticky header inside a scroll pane needs an opaque background. With a transparent one the list scrolls visibly through the text.
overscroll-behaviormust go on the SCROLLING element, not its parent. On the wrapper it does nothing.[scrollbar-gutter:stable]only helps where scrollbars take layout space; on overlay-scrollbar platforms it is a no-op, which is fine.- Remember the thread pane needs the same three utilities. Containing only the sidebar fixes half the problem.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
overscroll-behavior is Baseline 2022 and scrollbar-gutter is Baseline 2024 (Safari 17.4) — on older Safari the gutter utility is ignored and the list shifts slightly when the scrollbar appears, with no other consequence. Tailwind v4's own floor is higher than the containment's.