20 / 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 Sidebar Footer Pinning with Scrollable Nav
Nineteen nav items and a user card that has to stay visible: a three-row flex column where the middle row scrolls and the outer two do not. The whole mechanism is flex-none / flex-1 min-h-0 / flex-none, and min-h-0 is the utility that makes it work — the same one demo 08 needs, in the vertical direction.
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-20 block relative w-full min-h-svh bg-tsb20-bg text-tsb20-ink font-tsb20-sans leading-[1.55]" aria-label="Orbit workspace">
<div class="absolute top-3.5 right-3.5 z-60">
<input class="tsb-20-theme-input peer absolute inset-0 w-full h-full m-0 opacity-0 cursor-pointer" type="checkbox" id="tsb-20-theme">
<label class="grid place-items-center w-11 h-11 border border-tsb20-line bg-tsb20-surf text-tsb20-ink cursor-pointer peer-focus-visible:outline-2 peer-focus-visible:outline-tsb20-acc peer-focus-visible:outline-offset-2" for="tsb-20-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-tsb20-surf max-r34:flex-col max-r34:h-auto max-r34:min-h-svh">
<aside class="flex-none flex flex-col w-60 min-w-0 min-h-0 bg-tsb20-surf2 border-r border-tsb20-line max-r34:w-full max-r34:h-[60vh] max-r34:border-r-0 max-r34:border-b">
<div class="flex-none px-4 py-3.5 border-b border-tsb20-line"><p class="m-0 font-tsb20-display text-[1.15rem] tracking-[-0.01em]">Orbit</p></div>
<nav class="flex-1 min-h-0 overflow-y-auto px-1.5 py-1.5 [overscroll-behavior:contain] [scrollbar-gutter:stable]" aria-label="Orbit workspace">
<ul class="list-none m-0 p-0 [&_a]:flex [&_a]:items-center [&_a]:min-w-0 [&_a]:min-h-11 [&_a]:px-2.5 [&_a]:py-2 [&_a]:text-[0.9rem] [&_a]:text-tsb20-ink [&_a]:no-underline [&_a:hover]:bg-tsb20-surf [&_a:focus-visible]:outline-2 [&_a:focus-visible]:outline-tsb20-acc [&_a:focus-visible]:-outline-offset-2 [&_a[aria-current=page]]:bg-tsb20-accsoft [&_a[aria-current=page]]:text-tsb20-acc [&_a[aria-current=page]]:font-semibold [&_a[aria-current=page]]:shadow-[inset_3px_0_0_var(--color-tsb20-acc)]">
<li><a href="#tsb-20-main" aria-current="page">Overview</a></li>
<li><a href="#tsb-20-main">Projects</a></li>
<li><a href="#tsb-20-main">Deployments</a></li>
<li><a href="#tsb-20-main">Team settings</a></li>
<li><a href="#tsb-20-main">Audit log</a></li>
<li><a href="#tsb-20-main">Billing</a></li>
<li><a href="#tsb-20-main">API keys</a></li>
<li><a href="#tsb-20-main">Integrations</a></li>
<li><a href="#tsb-20-main">Webhooks</a></li>
<li><a href="#tsb-20-main">Regions</a></li>
<li><a href="#tsb-20-main">Certificates</a></li>
<li><a href="#tsb-20-main">Environments</a></li>
<li><a href="#tsb-20-main">Secrets</a></li>
<li><a href="#tsb-20-main">Usage</a></li>
<li><a href="#tsb-20-main">Invoices</a></li>
<li><a href="#tsb-20-main">Support</a></li>
<li><a href="#tsb-20-main">Status</a></li>
<li><a href="#tsb-20-main">Changelog</a></li>
<li><a href="#tsb-20-main">Archive</a></li>
</ul>
</nav>
<div class="flex-none flex items-center gap-2.5 px-3.5 py-3 border-t border-tsb20-line">
<span class="flex-none grid place-items-center w-9 h-9 bg-tsb20-acc text-tsb20-onacc text-[0.76rem] font-bold tracking-[0.04em]" aria-hidden="true">JL</span>
<span class="grid min-w-0 text-[0.76rem] text-tsb20-mut"><b class="text-[0.88rem] font-semibold text-tsb20-ink truncate">Jordan Lee</b>Operations</span>
</div>
</aside>
<main class="flex-1 min-w-0 min-h-0 overflow-y-auto px-5 pt-6 pb-10 r46:px-9 r46:pt-10 r46:pb-12 max-r34:overflow-visible" id="tsb-20-main">
<h2 class="m-0 mb-5 font-tsb20-display text-[clamp(1.45rem,3.6vw,2.1rem)] leading-[1.1]">Overview</h2>
<div class="grid grid-cols-[minmax(0,1fr)] gap-3 mb-5 max-w-[46rem] r34:grid-cols-3">
<div class="min-w-0 px-4 py-3.5 border border-tsb20-line"><p class="m-0 mb-1 text-[0.72rem] tracking-[0.1em] uppercase text-tsb20-mut">Open projects</p><p class="m-0 font-tsb20-display text-[1.5rem] tabular-nums">14</p></div>
<div class="min-w-0 px-4 py-3.5 border border-tsb20-line"><p class="m-0 mb-1 text-[0.72rem] tracking-[0.1em] uppercase text-tsb20-mut">Deployments today</p><p class="m-0 font-tsb20-display text-[1.5rem] tabular-nums">9</p></div>
<div class="min-w-0 px-4 py-3.5 border border-tsb20-line"><p class="m-0 mb-1 text-[0.72rem] tracking-[0.1em] uppercase text-tsb20-mut">Members</p><p class="m-0 font-tsb20-display text-[1.5rem] tabular-nums">38</p></div>
</div>
<ul class="list-none m-0 p-0 grid gap-px max-w-[46rem] bg-tsb20-line border border-tsb20-line [&>li]:flex [&>li]:gap-3 [&>li]:min-w-0 [&>li]:px-3.5 [&>li]:py-2.5 [&>li]:bg-tsb20-surf [&>li]:text-[0.9rem] [&>li>span]:min-w-0 [&>li>span]:truncate [&>li>em]:ml-auto [&>li>em]:not-italic [&>li>em]:text-[0.82rem] [&>li>em]:text-tsb20-mut">
<li><span>northwind-api</span><em>Promoted 09:14</em></li>
<li><span>ledger-web</span><em>Building</em></li>
<li><span>fern-cms</span><em>Promoted 07:41</em></li>
<li><span>talon-editor</span><em>Rolled back</em></li>
</ul>
</main>
</div>
</section><section class="tsb-20 block relative w-full min-h-svh bg-tsb20-bg text-tsb20-ink font-tsb20-sans leading-[1.55]" aria-label="Orbit workspace">
<div class="absolute top-3.5 right-3.5 z-60">
<input class="tsb-20-theme-input peer absolute inset-0 w-full h-full m-0 opacity-0 cursor-pointer" type="checkbox" id="tsb-20-theme">
<label class="grid place-items-center w-11 h-11 border border-tsb20-line bg-tsb20-surf text-tsb20-ink cursor-pointer peer-focus-visible:outline-2 peer-focus-visible:outline-tsb20-acc peer-focus-visible:outline-offset-2" for="tsb-20-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-tsb20-surf max-r34:flex-col max-r34:h-auto max-r34:min-h-svh">
<aside class="flex-none flex flex-col w-60 min-w-0 min-h-0 bg-tsb20-surf2 border-r border-tsb20-line max-r34:w-full max-r34:h-[60vh] max-r34:border-r-0 max-r34:border-b">
<div class="flex-none px-4 py-3.5 border-b border-tsb20-line"><p class="m-0 font-tsb20-display text-[1.15rem] tracking-[-0.01em]">Orbit</p></div>
<nav class="flex-1 min-h-0 overflow-y-auto px-1.5 py-1.5 [overscroll-behavior:contain] [scrollbar-gutter:stable]" aria-label="Orbit workspace">
<ul class="list-none m-0 p-0 [&_a]:flex [&_a]:items-center [&_a]:min-w-0 [&_a]:min-h-11 [&_a]:px-2.5 [&_a]:py-2 [&_a]:text-[0.9rem] [&_a]:text-tsb20-ink [&_a]:no-underline [&_a:hover]:bg-tsb20-surf [&_a:focus-visible]:outline-2 [&_a:focus-visible]:outline-tsb20-acc [&_a:focus-visible]:-outline-offset-2 [&_a[aria-current=page]]:bg-tsb20-accsoft [&_a[aria-current=page]]:text-tsb20-acc [&_a[aria-current=page]]:font-semibold [&_a[aria-current=page]]:shadow-[inset_3px_0_0_var(--color-tsb20-acc)]">
<li><a href="#tsb-20-main" aria-current="page">Overview</a></li>
<li><a href="#tsb-20-main">Projects</a></li>
<li><a href="#tsb-20-main">Deployments</a></li>
<li><a href="#tsb-20-main">Team settings</a></li>
<li><a href="#tsb-20-main">Audit log</a></li>
<li><a href="#tsb-20-main">Billing</a></li>
<li><a href="#tsb-20-main">API keys</a></li>
<li><a href="#tsb-20-main">Integrations</a></li>
<li><a href="#tsb-20-main">Webhooks</a></li>
<li><a href="#tsb-20-main">Regions</a></li>
<li><a href="#tsb-20-main">Certificates</a></li>
<li><a href="#tsb-20-main">Environments</a></li>
<li><a href="#tsb-20-main">Secrets</a></li>
<li><a href="#tsb-20-main">Usage</a></li>
<li><a href="#tsb-20-main">Invoices</a></li>
<li><a href="#tsb-20-main">Support</a></li>
<li><a href="#tsb-20-main">Status</a></li>
<li><a href="#tsb-20-main">Changelog</a></li>
<li><a href="#tsb-20-main">Archive</a></li>
</ul>
</nav>
<div class="flex-none flex items-center gap-2.5 px-3.5 py-3 border-t border-tsb20-line">
<span class="flex-none grid place-items-center w-9 h-9 bg-tsb20-acc text-tsb20-onacc text-[0.76rem] font-bold tracking-[0.04em]" aria-hidden="true">JL</span>
<span class="grid min-w-0 text-[0.76rem] text-tsb20-mut"><b class="text-[0.88rem] font-semibold text-tsb20-ink truncate">Jordan Lee</b>Operations</span>
</div>
</aside>
<main class="flex-1 min-w-0 min-h-0 overflow-y-auto px-5 pt-6 pb-10 r46:px-9 r46:pt-10 r46:pb-12 max-r34:overflow-visible" id="tsb-20-main">
<h2 class="m-0 mb-5 font-tsb20-display text-[clamp(1.45rem,3.6vw,2.1rem)] leading-[1.1]">Overview</h2>
<div class="grid grid-cols-[minmax(0,1fr)] gap-3 mb-5 max-w-[46rem] r34:grid-cols-3">
<div class="min-w-0 px-4 py-3.5 border border-tsb20-line"><p class="m-0 mb-1 text-[0.72rem] tracking-[0.1em] uppercase text-tsb20-mut">Open projects</p><p class="m-0 font-tsb20-display text-[1.5rem] tabular-nums">14</p></div>
<div class="min-w-0 px-4 py-3.5 border border-tsb20-line"><p class="m-0 mb-1 text-[0.72rem] tracking-[0.1em] uppercase text-tsb20-mut">Deployments today</p><p class="m-0 font-tsb20-display text-[1.5rem] tabular-nums">9</p></div>
<div class="min-w-0 px-4 py-3.5 border border-tsb20-line"><p class="m-0 mb-1 text-[0.72rem] tracking-[0.1em] uppercase text-tsb20-mut">Members</p><p class="m-0 font-tsb20-display text-[1.5rem] tabular-nums">38</p></div>
</div>
<ul class="list-none m-0 p-0 grid gap-px max-w-[46rem] bg-tsb20-line border border-tsb20-line [&>li]:flex [&>li]:gap-3 [&>li]:min-w-0 [&>li]:px-3.5 [&>li]:py-2.5 [&>li]:bg-tsb20-surf [&>li]:text-[0.9rem] [&>li>span]:min-w-0 [&>li>span]:truncate [&>li>em]:ml-auto [&>li>em]:not-italic [&>li>em]:text-[0.82rem] [&>li>em]:text-tsb20-mut">
<li><span>northwind-api</span><em>Promoted 09:14</em></li>
<li><span>ledger-web</span><em>Building</em></li>
<li><span>fern-cms</span><em>Promoted 07:41</em></li>
<li><span>talon-editor</span><em>Rolled back</em></li>
</ul>
</main>
</div>
</section>@import "tailwindcss";
@theme {
--color-tsb20-bg: #f6f4ef;
--color-tsb20-bg: oklch(96% 0.01 90);
--color-tsb20-surf: #fffdf9;
--color-tsb20-surf: oklch(99% 0.006 90);
--color-tsb20-surf2: #ece7dd;
--color-tsb20-surf2: oklch(92.5% 0.014 85);
--color-tsb20-line: #d9d2c5;
--color-tsb20-line: oklch(86% 0.018 85);
--color-tsb20-ink: #1e1c18;
--color-tsb20-ink: oklch(23% 0.008 80);
--color-tsb20-mut: #665f54;
--color-tsb20-mut: oklch(46% 0.014 85);
--color-tsb20-acc: #2f4f7a;
--color-tsb20-acc: oklch(42% 0.1 250);
--color-tsb20-accsoft: #dde5f0;
--color-tsb20-accsoft: oklch(91% 0.02 250);
--color-tsb20-onacc: #fffdf9;
--color-tsb20-onacc: oklch(99% 0.006 90);
--font-tsb20-sans: system-ui, -apple-system, 'Segoe UI', sans-serif;
--font-tsb20-display: 'Roboto Slab', Rockwell, 'Bookman Old Style', Georgia, serif;
--breakpoint-r34: 34rem;
--breakpoint-r46: 46rem;
}
/* Root rule: the full-bleed contract. The app shell inside it is h-svh — a bounded
height is what lets the middle flex row scroll and the footer stay pinned. */
.tsb-20 {
width: 100%;
min-height: 100svh;
}
.tsb-20 *,
.tsb-20 *::before,
.tsb-20 *::after {
box-sizing: border-box;
}
/* Token flip for the pure-CSS theme toggle, plus the two mandatory mirror blocks. */
.tsb-20:has(.tsb-20-theme-input:checked) {
--color-tsb20-bg: #131210;
--color-tsb20-surf: #1a1916;
--color-tsb20-surf2: #221f1b;
--color-tsb20-line: #302c26;
--color-tsb20-ink: #efece5;
--color-tsb20-mut: #a29a8d;
--color-tsb20-acc: #93b4dd;
--color-tsb20-acc: oklch(78% 0.08 250);
--color-tsb20-accsoft: #1c2635;
--color-tsb20-onacc: #131210;
}
@media (prefers-color-scheme: dark) {
.tsb-20 {
--color-tsb20-bg: #131210;
--color-tsb20-surf: #1a1916;
--color-tsb20-surf2: #221f1b;
--color-tsb20-line: #302c26;
--color-tsb20-ink: #efece5;
--color-tsb20-mut: #a29a8d;
--color-tsb20-acc: #93b4dd;
--color-tsb20-acc: oklch(78% 0.08 250);
--color-tsb20-accsoft: #1c2635;
--color-tsb20-onacc: #131210;
}
.tsb-20:has(.tsb-20-theme-input:checked) {
--color-tsb20-bg: #f6f4ef;
--color-tsb20-surf: #fffdf9;
--color-tsb20-surf2: #ece7dd;
--color-tsb20-line: #d9d2c5;
--color-tsb20-ink: #1e1c18;
--color-tsb20-mut: #665f54;
--color-tsb20-acc: #2f4f7a;
--color-tsb20-acc: oklch(42% 0.1 250);
--color-tsb20-accsoft: #dde5f0;
--color-tsb20-onacc: #fffdf9;
}
}
@media (prefers-color-scheme: light) {
.tsb-20:has(.tsb-20-theme-input:checked) {
--color-tsb20-bg: #131210;
--color-tsb20-surf: #1a1916;
--color-tsb20-surf2: #221f1b;
--color-tsb20-line: #302c26;
--color-tsb20-ink: #efece5;
--color-tsb20-mut: #a29a8d;
--color-tsb20-acc: #93b4dd;
--color-tsb20-acc: oklch(78% 0.08 250);
--color-tsb20-accsoft: #1c2635;
--color-tsb20-onacc: #131210;
}
}@import "tailwindcss";
@theme {
--color-tsb20-bg: #f6f4ef;
--color-tsb20-bg: oklch(96% 0.01 90);
--color-tsb20-surf: #fffdf9;
--color-tsb20-surf: oklch(99% 0.006 90);
--color-tsb20-surf2: #ece7dd;
--color-tsb20-surf2: oklch(92.5% 0.014 85);
--color-tsb20-line: #d9d2c5;
--color-tsb20-line: oklch(86% 0.018 85);
--color-tsb20-ink: #1e1c18;
--color-tsb20-ink: oklch(23% 0.008 80);
--color-tsb20-mut: #665f54;
--color-tsb20-mut: oklch(46% 0.014 85);
--color-tsb20-acc: #2f4f7a;
--color-tsb20-acc: oklch(42% 0.1 250);
--color-tsb20-accsoft: #dde5f0;
--color-tsb20-accsoft: oklch(91% 0.02 250);
--color-tsb20-onacc: #fffdf9;
--color-tsb20-onacc: oklch(99% 0.006 90);
--font-tsb20-sans: system-ui, -apple-system, 'Segoe UI', sans-serif;
--font-tsb20-display: 'Roboto Slab', Rockwell, 'Bookman Old Style', Georgia, serif;
--breakpoint-r34: 34rem;
--breakpoint-r46: 46rem;
}
/* Root rule: the full-bleed contract. The app shell inside it is h-svh — a bounded
height is what lets the middle flex row scroll and the footer stay pinned. */
.tsb-20 {
width: 100%;
min-height: 100svh;
}
.tsb-20 *,
.tsb-20 *::before,
.tsb-20 *::after {
box-sizing: border-box;
}
/* Token flip for the pure-CSS theme toggle, plus the two mandatory mirror blocks. */
.tsb-20:has(.tsb-20-theme-input:checked) {
--color-tsb20-bg: #131210;
--color-tsb20-surf: #1a1916;
--color-tsb20-surf2: #221f1b;
--color-tsb20-line: #302c26;
--color-tsb20-ink: #efece5;
--color-tsb20-mut: #a29a8d;
--color-tsb20-acc: #93b4dd;
--color-tsb20-acc: oklch(78% 0.08 250);
--color-tsb20-accsoft: #1c2635;
--color-tsb20-onacc: #131210;
}
@media (prefers-color-scheme: dark) {
.tsb-20 {
--color-tsb20-bg: #131210;
--color-tsb20-surf: #1a1916;
--color-tsb20-surf2: #221f1b;
--color-tsb20-line: #302c26;
--color-tsb20-ink: #efece5;
--color-tsb20-mut: #a29a8d;
--color-tsb20-acc: #93b4dd;
--color-tsb20-acc: oklch(78% 0.08 250);
--color-tsb20-accsoft: #1c2635;
--color-tsb20-onacc: #131210;
}
.tsb-20:has(.tsb-20-theme-input:checked) {
--color-tsb20-bg: #f6f4ef;
--color-tsb20-surf: #fffdf9;
--color-tsb20-surf2: #ece7dd;
--color-tsb20-line: #d9d2c5;
--color-tsb20-ink: #1e1c18;
--color-tsb20-mut: #665f54;
--color-tsb20-acc: #2f4f7a;
--color-tsb20-acc: oklch(42% 0.1 250);
--color-tsb20-accsoft: #dde5f0;
--color-tsb20-onacc: #fffdf9;
}
}
@media (prefers-color-scheme: light) {
.tsb-20:has(.tsb-20-theme-input:checked) {
--color-tsb20-bg: #131210;
--color-tsb20-surf: #1a1916;
--color-tsb20-surf2: #221f1b;
--color-tsb20-line: #302c26;
--color-tsb20-ink: #efece5;
--color-tsb20-mut: #a29a8d;
--color-tsb20-acc: #93b4dd;
--color-tsb20-acc: oklch(78% 0.08 250);
--color-tsb20-accsoft: #1c2635;
--color-tsb20-onacc: #131210;
}
}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-tsb20-bg: #f6f4ef;
--color-tsb20-bg: oklch(96% 0.01 90);
--color-tsb20-surf: #fffdf9;
--color-tsb20-surf: oklch(99% 0.006 90);
--color-tsb20-surf2: #ece7dd;
--color-tsb20-surf2: oklch(92.5% 0.014 85);
--color-tsb20-line: #d9d2c5;
--color-tsb20-line: oklch(86% 0.018 85);
--color-tsb20-ink: #1e1c18;
--color-tsb20-ink: oklch(23% 0.008 80);
--color-tsb20-mut: #665f54;
--color-tsb20-mut: oklch(46% 0.014 85);
--color-tsb20-acc: #2f4f7a;
--color-tsb20-acc: oklch(42% 0.1 250);
--color-tsb20-accsoft: #dde5f0;
--color-tsb20-accsoft: oklch(91% 0.02 250);
--color-tsb20-onacc: #fffdf9;
--color-tsb20-onacc: oklch(99% 0.006 90);
--font-tsb20-sans: system-ui, -apple-system, 'Segoe UI', sans-serif;
--font-tsb20-display: 'Roboto Slab', Rockwell, 'Bookman Old Style', Georgia, serif;
--breakpoint-r34: 34rem;
--breakpoint-r46: 46rem;
}
/* Root rule: the full-bleed contract. The app shell inside it is h-svh — a bounded
height is what lets the middle flex row scroll and the footer stay pinned. */
.tsb-20 {
width: 100%;
min-height: 100svh;
}
.tsb-20 *,
.tsb-20 *::before,
.tsb-20 *::after {
box-sizing: border-box;
}
/* Token flip for the pure-CSS theme toggle, plus the two mandatory mirror blocks. */
.tsb-20:has(.tsb-20-theme-input:checked) {
--color-tsb20-bg: #131210;
--color-tsb20-surf: #1a1916;
--color-tsb20-surf2: #221f1b;
--color-tsb20-line: #302c26;
--color-tsb20-ink: #efece5;
--color-tsb20-mut: #a29a8d;
--color-tsb20-acc: #93b4dd;
--color-tsb20-acc: oklch(78% 0.08 250);
--color-tsb20-accsoft: #1c2635;
--color-tsb20-onacc: #131210;
}
@media (prefers-color-scheme: dark) {
.tsb-20 {
--color-tsb20-bg: #131210;
--color-tsb20-surf: #1a1916;
--color-tsb20-surf2: #221f1b;
--color-tsb20-line: #302c26;
--color-tsb20-ink: #efece5;
--color-tsb20-mut: #a29a8d;
--color-tsb20-acc: #93b4dd;
--color-tsb20-acc: oklch(78% 0.08 250);
--color-tsb20-accsoft: #1c2635;
--color-tsb20-onacc: #131210;
}
.tsb-20:has(.tsb-20-theme-input:checked) {
--color-tsb20-bg: #f6f4ef;
--color-tsb20-surf: #fffdf9;
--color-tsb20-surf2: #ece7dd;
--color-tsb20-line: #d9d2c5;
--color-tsb20-ink: #1e1c18;
--color-tsb20-mut: #665f54;
--color-tsb20-acc: #2f4f7a;
--color-tsb20-acc: oklch(42% 0.1 250);
--color-tsb20-accsoft: #dde5f0;
--color-tsb20-onacc: #fffdf9;
}
}
@media (prefers-color-scheme: light) {
.tsb-20:has(.tsb-20-theme-input:checked) {
--color-tsb20-bg: #131210;
--color-tsb20-surf: #1a1916;
--color-tsb20-surf2: #221f1b;
--color-tsb20-line: #302c26;
--color-tsb20-ink: #efece5;
--color-tsb20-mut: #a29a8d;
--color-tsb20-acc: #93b4dd;
--color-tsb20-acc: oklch(78% 0.08 250);
--color-tsb20-accsoft: #1c2635;
--color-tsb20-onacc: #131210;
}
}@import "tailwindcss";
@theme {
--color-tsb20-bg: #f6f4ef;
--color-tsb20-bg: oklch(96% 0.01 90);
--color-tsb20-surf: #fffdf9;
--color-tsb20-surf: oklch(99% 0.006 90);
--color-tsb20-surf2: #ece7dd;
--color-tsb20-surf2: oklch(92.5% 0.014 85);
--color-tsb20-line: #d9d2c5;
--color-tsb20-line: oklch(86% 0.018 85);
--color-tsb20-ink: #1e1c18;
--color-tsb20-ink: oklch(23% 0.008 80);
--color-tsb20-mut: #665f54;
--color-tsb20-mut: oklch(46% 0.014 85);
--color-tsb20-acc: #2f4f7a;
--color-tsb20-acc: oklch(42% 0.1 250);
--color-tsb20-accsoft: #dde5f0;
--color-tsb20-accsoft: oklch(91% 0.02 250);
--color-tsb20-onacc: #fffdf9;
--color-tsb20-onacc: oklch(99% 0.006 90);
--font-tsb20-sans: system-ui, -apple-system, 'Segoe UI', sans-serif;
--font-tsb20-display: 'Roboto Slab', Rockwell, 'Bookman Old Style', Georgia, serif;
--breakpoint-r34: 34rem;
--breakpoint-r46: 46rem;
}
/* Root rule: the full-bleed contract. The app shell inside it is h-svh — a bounded
height is what lets the middle flex row scroll and the footer stay pinned. */
.tsb-20 {
width: 100%;
min-height: 100svh;
}
.tsb-20 *,
.tsb-20 *::before,
.tsb-20 *::after {
box-sizing: border-box;
}
/* Token flip for the pure-CSS theme toggle, plus the two mandatory mirror blocks. */
.tsb-20:has(.tsb-20-theme-input:checked) {
--color-tsb20-bg: #131210;
--color-tsb20-surf: #1a1916;
--color-tsb20-surf2: #221f1b;
--color-tsb20-line: #302c26;
--color-tsb20-ink: #efece5;
--color-tsb20-mut: #a29a8d;
--color-tsb20-acc: #93b4dd;
--color-tsb20-acc: oklch(78% 0.08 250);
--color-tsb20-accsoft: #1c2635;
--color-tsb20-onacc: #131210;
}
@media (prefers-color-scheme: dark) {
.tsb-20 {
--color-tsb20-bg: #131210;
--color-tsb20-surf: #1a1916;
--color-tsb20-surf2: #221f1b;
--color-tsb20-line: #302c26;
--color-tsb20-ink: #efece5;
--color-tsb20-mut: #a29a8d;
--color-tsb20-acc: #93b4dd;
--color-tsb20-acc: oklch(78% 0.08 250);
--color-tsb20-accsoft: #1c2635;
--color-tsb20-onacc: #131210;
}
.tsb-20:has(.tsb-20-theme-input:checked) {
--color-tsb20-bg: #f6f4ef;
--color-tsb20-surf: #fffdf9;
--color-tsb20-surf2: #ece7dd;
--color-tsb20-line: #d9d2c5;
--color-tsb20-ink: #1e1c18;
--color-tsb20-mut: #665f54;
--color-tsb20-acc: #2f4f7a;
--color-tsb20-acc: oklch(42% 0.1 250);
--color-tsb20-accsoft: #dde5f0;
--color-tsb20-onacc: #fffdf9;
}
}
@media (prefers-color-scheme: light) {
.tsb-20:has(.tsb-20-theme-input:checked) {
--color-tsb20-bg: #131210;
--color-tsb20-surf: #1a1916;
--color-tsb20-surf2: #221f1b;
--color-tsb20-line: #302c26;
--color-tsb20-ink: #efece5;
--color-tsb20-mut: #a29a8d;
--color-tsb20-acc: #93b4dd;
--color-tsb20-acc: oklch(78% 0.08 250);
--color-tsb20-accsoft: #1c2635;
--color-tsb20-onacc: #131210;
}
}<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;500;600&display=swap">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto+Slab:wght@400;500;600&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
- The CSS version needed a comment to explain that
min-block-size: 0is the load-bearing line. In utilities it ismin-h-0sitting betweenflex-1andoverflow-y-auto, which is the most legible this pattern gets in any syntax. - Nineteen links are styled with descendant arbitrary variants on the
<ul>([&_a]:,[&_a[aria-current=page]]:), the same choice as demo 08 and for the same reason: at this count, parent-level utilities are the honest port. truncateplusmin-w-0on the parent is the inline-axis twin offlex-1 min-h-0— the collection's two most commonly missed utilities, in one demo.- No
positionanywhere in the rail. The footer is pinned by flex distribution, which survives content changes better than a sticky or absolute footer. - The phone layout is three
max-r34:overrides on the shell and the rail, replacing a media-query block — and the60vhcompromise is stated on the element rather than buried at the end of the stylesheet. - Final stylesheet: tokens, root contract, theme flip. Twenty demos, and the same three things are all that is left in CSS.
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 Sidebar Footer Pinning with Scrollable Nav
Source: https://codefronts.com/navigation/tailwind-css-sidebars/pure-tailwind-sidebar-footer-pinning-with-scrollable-nav/
Nineteen nav items and a user card that has to stay visible: a three-row flex column where the middle row scrolls and the outer two do not. The whole mechanism is flex-none / flex-1 min-h-0 / flex-none, and min-h-0 is the utility that makes it work — the same one demo 08 needs, in the vertical direction.
## HTML
```html
<section class="tsb-20 block relative w-full min-h-svh bg-tsb20-bg text-tsb20-ink font-tsb20-sans leading-[1.55]" aria-label="Orbit workspace">
<div class="absolute top-3.5 right-3.5 z-60">
<input class="tsb-20-theme-input peer absolute inset-0 w-full h-full m-0 opacity-0 cursor-pointer" type="checkbox" id="tsb-20-theme">
<label class="grid place-items-center w-11 h-11 border border-tsb20-line bg-tsb20-surf text-tsb20-ink cursor-pointer peer-focus-visible:outline-2 peer-focus-visible:outline-tsb20-acc peer-focus-visible:outline-offset-2" for="tsb-20-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-tsb20-surf max-r34:flex-col max-r34:h-auto max-r34:min-h-svh">
<aside class="flex-none flex flex-col w-60 min-w-0 min-h-0 bg-tsb20-surf2 border-r border-tsb20-line max-r34:w-full max-r34:h-[60vh] max-r34:border-r-0 max-r34:border-b">
<div class="flex-none px-4 py-3.5 border-b border-tsb20-line"><p class="m-0 font-tsb20-display text-[1.15rem] tracking-[-0.01em]">Orbit</p></div>
<nav class="flex-1 min-h-0 overflow-y-auto px-1.5 py-1.5 [overscroll-behavior:contain] [scrollbar-gutter:stable]" aria-label="Orbit workspace">
<ul class="list-none m-0 p-0 [&_a]:flex [&_a]:items-center [&_a]:min-w-0 [&_a]:min-h-11 [&_a]:px-2.5 [&_a]:py-2 [&_a]:text-[0.9rem] [&_a]:text-tsb20-ink [&_a]:no-underline [&_a:hover]:bg-tsb20-surf [&_a:focus-visible]:outline-2 [&_a:focus-visible]:outline-tsb20-acc [&_a:focus-visible]:-outline-offset-2 [&_a[aria-current=page]]:bg-tsb20-accsoft [&_a[aria-current=page]]:text-tsb20-acc [&_a[aria-current=page]]:font-semibold [&_a[aria-current=page]]:shadow-[inset_3px_0_0_var(--color-tsb20-acc)]">
<li><a href="#tsb-20-main" aria-current="page">Overview</a></li>
<li><a href="#tsb-20-main">Projects</a></li>
<li><a href="#tsb-20-main">Deployments</a></li>
<li><a href="#tsb-20-main">Team settings</a></li>
<li><a href="#tsb-20-main">Audit log</a></li>
<li><a href="#tsb-20-main">Billing</a></li>
<li><a href="#tsb-20-main">API keys</a></li>
<li><a href="#tsb-20-main">Integrations</a></li>
<li><a href="#tsb-20-main">Webhooks</a></li>
<li><a href="#tsb-20-main">Regions</a></li>
<li><a href="#tsb-20-main">Certificates</a></li>
<li><a href="#tsb-20-main">Environments</a></li>
<li><a href="#tsb-20-main">Secrets</a></li>
<li><a href="#tsb-20-main">Usage</a></li>
<li><a href="#tsb-20-main">Invoices</a></li>
<li><a href="#tsb-20-main">Support</a></li>
<li><a href="#tsb-20-main">Status</a></li>
<li><a href="#tsb-20-main">Changelog</a></li>
<li><a href="#tsb-20-main">Archive</a></li>
</ul>
</nav>
<div class="flex-none flex items-center gap-2.5 px-3.5 py-3 border-t border-tsb20-line">
<span class="flex-none grid place-items-center w-9 h-9 bg-tsb20-acc text-tsb20-onacc text-[0.76rem] font-bold tracking-[0.04em]" aria-hidden="true">JL</span>
<span class="grid min-w-0 text-[0.76rem] text-tsb20-mut"><b class="text-[0.88rem] font-semibold text-tsb20-ink truncate">Jordan Lee</b>Operations</span>
</div>
</aside>
<main class="flex-1 min-w-0 min-h-0 overflow-y-auto px-5 pt-6 pb-10 r46:px-9 r46:pt-10 r46:pb-12 max-r34:overflow-visible" id="tsb-20-main">
<h2 class="m-0 mb-5 font-tsb20-display text-[clamp(1.45rem,3.6vw,2.1rem)] leading-[1.1]">Overview</h2>
<div class="grid grid-cols-[minmax(0,1fr)] gap-3 mb-5 max-w-[46rem] r34:grid-cols-3">
<div class="min-w-0 px-4 py-3.5 border border-tsb20-line"><p class="m-0 mb-1 text-[0.72rem] tracking-[0.1em] uppercase text-tsb20-mut">Open projects</p><p class="m-0 font-tsb20-display text-[1.5rem] tabular-nums">14</p></div>
<div class="min-w-0 px-4 py-3.5 border border-tsb20-line"><p class="m-0 mb-1 text-[0.72rem] tracking-[0.1em] uppercase text-tsb20-mut">Deployments today</p><p class="m-0 font-tsb20-display text-[1.5rem] tabular-nums">9</p></div>
<div class="min-w-0 px-4 py-3.5 border border-tsb20-line"><p class="m-0 mb-1 text-[0.72rem] tracking-[0.1em] uppercase text-tsb20-mut">Members</p><p class="m-0 font-tsb20-display text-[1.5rem] tabular-nums">38</p></div>
</div>
<ul class="list-none m-0 p-0 grid gap-px max-w-[46rem] bg-tsb20-line border border-tsb20-line [&>li]:flex [&>li]:gap-3 [&>li]:min-w-0 [&>li]:px-3.5 [&>li]:py-2.5 [&>li]:bg-tsb20-surf [&>li]:text-[0.9rem] [&>li>span]:min-w-0 [&>li>span]:truncate [&>li>em]:ml-auto [&>li>em]:not-italic [&>li>em]:text-[0.82rem] [&>li>em]:text-tsb20-mut">
<li><span>northwind-api</span><em>Promoted 09:14</em></li>
<li><span>ledger-web</span><em>Building</em></li>
<li><span>fern-cms</span><em>Promoted 07:41</em></li>
<li><span>talon-editor</span><em>Rolled back</em></li>
</ul>
</main>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tsb20-bg: #f6f4ef;
--color-tsb20-bg: oklch(96% 0.01 90);
--color-tsb20-surf: #fffdf9;
--color-tsb20-surf: oklch(99% 0.006 90);
--color-tsb20-surf2: #ece7dd;
--color-tsb20-surf2: oklch(92.5% 0.014 85);
--color-tsb20-line: #d9d2c5;
--color-tsb20-line: oklch(86% 0.018 85);
--color-tsb20-ink: #1e1c18;
--color-tsb20-ink: oklch(23% 0.008 80);
--color-tsb20-mut: #665f54;
--color-tsb20-mut: oklch(46% 0.014 85);
--color-tsb20-acc: #2f4f7a;
--color-tsb20-acc: oklch(42% 0.1 250);
--color-tsb20-accsoft: #dde5f0;
--color-tsb20-accsoft: oklch(91% 0.02 250);
--color-tsb20-onacc: #fffdf9;
--color-tsb20-onacc: oklch(99% 0.006 90);
--font-tsb20-sans: system-ui, -apple-system, 'Segoe UI', sans-serif;
--font-tsb20-display: 'Roboto Slab', Rockwell, 'Bookman Old Style', Georgia, serif;
--breakpoint-r34: 34rem;
--breakpoint-r46: 46rem;
}
/* Root rule: the full-bleed contract. The app shell inside it is h-svh — a bounded
height is what lets the middle flex row scroll and the footer stay pinned. */
.tsb-20 {
width: 100%;
min-height: 100svh;
}
.tsb-20 *,
.tsb-20 *::before,
.tsb-20 *::after {
box-sizing: border-box;
}
/* Token flip for the pure-CSS theme toggle, plus the two mandatory mirror blocks. */
.tsb-20:has(.tsb-20-theme-input:checked) {
--color-tsb20-bg: #131210;
--color-tsb20-surf: #1a1916;
--color-tsb20-surf2: #221f1b;
--color-tsb20-line: #302c26;
--color-tsb20-ink: #efece5;
--color-tsb20-mut: #a29a8d;
--color-tsb20-acc: #93b4dd;
--color-tsb20-acc: oklch(78% 0.08 250);
--color-tsb20-accsoft: #1c2635;
--color-tsb20-onacc: #131210;
}
@media (prefers-color-scheme: dark) {
.tsb-20 {
--color-tsb20-bg: #131210;
--color-tsb20-surf: #1a1916;
--color-tsb20-surf2: #221f1b;
--color-tsb20-line: #302c26;
--color-tsb20-ink: #efece5;
--color-tsb20-mut: #a29a8d;
--color-tsb20-acc: #93b4dd;
--color-tsb20-acc: oklch(78% 0.08 250);
--color-tsb20-accsoft: #1c2635;
--color-tsb20-onacc: #131210;
}
.tsb-20:has(.tsb-20-theme-input:checked) {
--color-tsb20-bg: #f6f4ef;
--color-tsb20-surf: #fffdf9;
--color-tsb20-surf2: #ece7dd;
--color-tsb20-line: #d9d2c5;
--color-tsb20-ink: #1e1c18;
--color-tsb20-mut: #665f54;
--color-tsb20-acc: #2f4f7a;
--color-tsb20-acc: oklch(42% 0.1 250);
--color-tsb20-accsoft: #dde5f0;
--color-tsb20-onacc: #fffdf9;
}
}
@media (prefers-color-scheme: light) {
.tsb-20:has(.tsb-20-theme-input:checked) {
--color-tsb20-bg: #131210;
--color-tsb20-surf: #1a1916;
--color-tsb20-surf2: #221f1b;
--color-tsb20-line: #302c26;
--color-tsb20-ink: #efece5;
--color-tsb20-mut: #a29a8d;
--color-tsb20-acc: #93b4dd;
--color-tsb20-acc: oklch(78% 0.08 250);
--color-tsb20-accsoft: #1c2635;
--color-tsb20-onacc: #131210;
}
}
```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 Sidebar Footer Pinning with Scrollable Nav
Source: https://codefronts.com/navigation/tailwind-css-sidebars/pure-tailwind-sidebar-footer-pinning-with-scrollable-nav/
Nineteen nav items and a user card that has to stay visible: a three-row flex column where the middle row scrolls and the outer two do not. The whole mechanism is flex-none / flex-1 min-h-0 / flex-none, and min-h-0 is the utility that makes it work — the same one demo 08 needs, in the vertical direction.
## HTML
```html
<section class="tsb-20 block relative w-full min-h-svh bg-tsb20-bg text-tsb20-ink font-tsb20-sans leading-[1.55]" aria-label="Orbit workspace">
<div class="absolute top-3.5 right-3.5 z-60">
<input class="tsb-20-theme-input peer absolute inset-0 w-full h-full m-0 opacity-0 cursor-pointer" type="checkbox" id="tsb-20-theme">
<label class="grid place-items-center w-11 h-11 border border-tsb20-line bg-tsb20-surf text-tsb20-ink cursor-pointer peer-focus-visible:outline-2 peer-focus-visible:outline-tsb20-acc peer-focus-visible:outline-offset-2" for="tsb-20-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-tsb20-surf max-r34:flex-col max-r34:h-auto max-r34:min-h-svh">
<aside class="flex-none flex flex-col w-60 min-w-0 min-h-0 bg-tsb20-surf2 border-r border-tsb20-line max-r34:w-full max-r34:h-[60vh] max-r34:border-r-0 max-r34:border-b">
<div class="flex-none px-4 py-3.5 border-b border-tsb20-line"><p class="m-0 font-tsb20-display text-[1.15rem] tracking-[-0.01em]">Orbit</p></div>
<nav class="flex-1 min-h-0 overflow-y-auto px-1.5 py-1.5 [overscroll-behavior:contain] [scrollbar-gutter:stable]" aria-label="Orbit workspace">
<ul class="list-none m-0 p-0 [&_a]:flex [&_a]:items-center [&_a]:min-w-0 [&_a]:min-h-11 [&_a]:px-2.5 [&_a]:py-2 [&_a]:text-[0.9rem] [&_a]:text-tsb20-ink [&_a]:no-underline [&_a:hover]:bg-tsb20-surf [&_a:focus-visible]:outline-2 [&_a:focus-visible]:outline-tsb20-acc [&_a:focus-visible]:-outline-offset-2 [&_a[aria-current=page]]:bg-tsb20-accsoft [&_a[aria-current=page]]:text-tsb20-acc [&_a[aria-current=page]]:font-semibold [&_a[aria-current=page]]:shadow-[inset_3px_0_0_var(--color-tsb20-acc)]">
<li><a href="#tsb-20-main" aria-current="page">Overview</a></li>
<li><a href="#tsb-20-main">Projects</a></li>
<li><a href="#tsb-20-main">Deployments</a></li>
<li><a href="#tsb-20-main">Team settings</a></li>
<li><a href="#tsb-20-main">Audit log</a></li>
<li><a href="#tsb-20-main">Billing</a></li>
<li><a href="#tsb-20-main">API keys</a></li>
<li><a href="#tsb-20-main">Integrations</a></li>
<li><a href="#tsb-20-main">Webhooks</a></li>
<li><a href="#tsb-20-main">Regions</a></li>
<li><a href="#tsb-20-main">Certificates</a></li>
<li><a href="#tsb-20-main">Environments</a></li>
<li><a href="#tsb-20-main">Secrets</a></li>
<li><a href="#tsb-20-main">Usage</a></li>
<li><a href="#tsb-20-main">Invoices</a></li>
<li><a href="#tsb-20-main">Support</a></li>
<li><a href="#tsb-20-main">Status</a></li>
<li><a href="#tsb-20-main">Changelog</a></li>
<li><a href="#tsb-20-main">Archive</a></li>
</ul>
</nav>
<div class="flex-none flex items-center gap-2.5 px-3.5 py-3 border-t border-tsb20-line">
<span class="flex-none grid place-items-center w-9 h-9 bg-tsb20-acc text-tsb20-onacc text-[0.76rem] font-bold tracking-[0.04em]" aria-hidden="true">JL</span>
<span class="grid min-w-0 text-[0.76rem] text-tsb20-mut"><b class="text-[0.88rem] font-semibold text-tsb20-ink truncate">Jordan Lee</b>Operations</span>
</div>
</aside>
<main class="flex-1 min-w-0 min-h-0 overflow-y-auto px-5 pt-6 pb-10 r46:px-9 r46:pt-10 r46:pb-12 max-r34:overflow-visible" id="tsb-20-main">
<h2 class="m-0 mb-5 font-tsb20-display text-[clamp(1.45rem,3.6vw,2.1rem)] leading-[1.1]">Overview</h2>
<div class="grid grid-cols-[minmax(0,1fr)] gap-3 mb-5 max-w-[46rem] r34:grid-cols-3">
<div class="min-w-0 px-4 py-3.5 border border-tsb20-line"><p class="m-0 mb-1 text-[0.72rem] tracking-[0.1em] uppercase text-tsb20-mut">Open projects</p><p class="m-0 font-tsb20-display text-[1.5rem] tabular-nums">14</p></div>
<div class="min-w-0 px-4 py-3.5 border border-tsb20-line"><p class="m-0 mb-1 text-[0.72rem] tracking-[0.1em] uppercase text-tsb20-mut">Deployments today</p><p class="m-0 font-tsb20-display text-[1.5rem] tabular-nums">9</p></div>
<div class="min-w-0 px-4 py-3.5 border border-tsb20-line"><p class="m-0 mb-1 text-[0.72rem] tracking-[0.1em] uppercase text-tsb20-mut">Members</p><p class="m-0 font-tsb20-display text-[1.5rem] tabular-nums">38</p></div>
</div>
<ul class="list-none m-0 p-0 grid gap-px max-w-[46rem] bg-tsb20-line border border-tsb20-line [&>li]:flex [&>li]:gap-3 [&>li]:min-w-0 [&>li]:px-3.5 [&>li]:py-2.5 [&>li]:bg-tsb20-surf [&>li]:text-[0.9rem] [&>li>span]:min-w-0 [&>li>span]:truncate [&>li>em]:ml-auto [&>li>em]:not-italic [&>li>em]:text-[0.82rem] [&>li>em]:text-tsb20-mut">
<li><span>northwind-api</span><em>Promoted 09:14</em></li>
<li><span>ledger-web</span><em>Building</em></li>
<li><span>fern-cms</span><em>Promoted 07:41</em></li>
<li><span>talon-editor</span><em>Rolled back</em></li>
</ul>
</main>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tsb20-bg: #f6f4ef;
--color-tsb20-bg: oklch(96% 0.01 90);
--color-tsb20-surf: #fffdf9;
--color-tsb20-surf: oklch(99% 0.006 90);
--color-tsb20-surf2: #ece7dd;
--color-tsb20-surf2: oklch(92.5% 0.014 85);
--color-tsb20-line: #d9d2c5;
--color-tsb20-line: oklch(86% 0.018 85);
--color-tsb20-ink: #1e1c18;
--color-tsb20-ink: oklch(23% 0.008 80);
--color-tsb20-mut: #665f54;
--color-tsb20-mut: oklch(46% 0.014 85);
--color-tsb20-acc: #2f4f7a;
--color-tsb20-acc: oklch(42% 0.1 250);
--color-tsb20-accsoft: #dde5f0;
--color-tsb20-accsoft: oklch(91% 0.02 250);
--color-tsb20-onacc: #fffdf9;
--color-tsb20-onacc: oklch(99% 0.006 90);
--font-tsb20-sans: system-ui, -apple-system, 'Segoe UI', sans-serif;
--font-tsb20-display: 'Roboto Slab', Rockwell, 'Bookman Old Style', Georgia, serif;
--breakpoint-r34: 34rem;
--breakpoint-r46: 46rem;
}
/* Root rule: the full-bleed contract. The app shell inside it is h-svh — a bounded
height is what lets the middle flex row scroll and the footer stay pinned. */
.tsb-20 {
width: 100%;
min-height: 100svh;
}
.tsb-20 *,
.tsb-20 *::before,
.tsb-20 *::after {
box-sizing: border-box;
}
/* Token flip for the pure-CSS theme toggle, plus the two mandatory mirror blocks. */
.tsb-20:has(.tsb-20-theme-input:checked) {
--color-tsb20-bg: #131210;
--color-tsb20-surf: #1a1916;
--color-tsb20-surf2: #221f1b;
--color-tsb20-line: #302c26;
--color-tsb20-ink: #efece5;
--color-tsb20-mut: #a29a8d;
--color-tsb20-acc: #93b4dd;
--color-tsb20-acc: oklch(78% 0.08 250);
--color-tsb20-accsoft: #1c2635;
--color-tsb20-onacc: #131210;
}
@media (prefers-color-scheme: dark) {
.tsb-20 {
--color-tsb20-bg: #131210;
--color-tsb20-surf: #1a1916;
--color-tsb20-surf2: #221f1b;
--color-tsb20-line: #302c26;
--color-tsb20-ink: #efece5;
--color-tsb20-mut: #a29a8d;
--color-tsb20-acc: #93b4dd;
--color-tsb20-acc: oklch(78% 0.08 250);
--color-tsb20-accsoft: #1c2635;
--color-tsb20-onacc: #131210;
}
.tsb-20:has(.tsb-20-theme-input:checked) {
--color-tsb20-bg: #f6f4ef;
--color-tsb20-surf: #fffdf9;
--color-tsb20-surf2: #ece7dd;
--color-tsb20-line: #d9d2c5;
--color-tsb20-ink: #1e1c18;
--color-tsb20-mut: #665f54;
--color-tsb20-acc: #2f4f7a;
--color-tsb20-acc: oklch(42% 0.1 250);
--color-tsb20-accsoft: #dde5f0;
--color-tsb20-onacc: #fffdf9;
}
}
@media (prefers-color-scheme: light) {
.tsb-20:has(.tsb-20-theme-input:checked) {
--color-tsb20-bg: #131210;
--color-tsb20-surf: #1a1916;
--color-tsb20-surf2: #221f1b;
--color-tsb20-line: #302c26;
--color-tsb20-ink: #efece5;
--color-tsb20-mut: #a29a8d;
--color-tsb20-acc: #93b4dd;
--color-tsb20-acc: oklch(78% 0.08 250);
--color-tsb20-accsoft: #1c2635;
--color-tsb20-onacc: #131210;
}
}
```How this works
No position: sticky, no absolute, no calc. A flex column with a bounded height distributes it: the head and footer take their content height and refuse to shrink, the nav takes the rest and scrolls.
<aside class="flex flex-col w-60 min-h-0">
<div class="flex-none">…brand…</div>
<nav class="flex-1 min-h-0 overflow-y-auto">…19 links…</nav>
<div class="flex-none">…user card…</div>min-h-0 is the whole trick, and it is invisible. A flex item's default min-height: auto refuses to shrink below its content, so flex-1 alone gives the nav the height of nineteen links, pushing the footer off the bottom. min-h-0 allows the bounded height, which is what lets overflow-y-auto engage.
Ellipsis needs two utilities in two places. truncate on the text and min-w-0 on its parent — a grid or flex child also defaults to a content-based minimum, in the inline direction this time. Same rule, other axis.
On a phone the rail stops being a rail. max-r34:h-[60vh] gives it a bounded scroll area above the content instead of a full-height column beside it.
Make it yours
- Rail width: the single
w-60. - Add a search field above the nav as another
flex-nonerow — the pattern takes any number of fixed rows. - Make the footer a real menu button rather than a static card; nothing about the layout changes.
- Retheme through
--color-tsb20-accand--color-tsb20-accsoft. - Swap the slab display face in
--font-tsb20-displayplus the font<link>. - Give the rail its own collapse with demo 11's checkbox or demo 07's persisted toggle; both compose with this layout unchanged.
Gotchas — read before shipping
flex-1withoutmin-h-0does not produce a scrollable row — the item's automatic minimum size keeps it at content height and the footer is pushed out of view.- The app shell needs a bounded height (
h-svh). Withmin-h-svhnothing is bounded and the whole page scrolls instead. truncatealone does nothing inside a flex or grid child withoutmin-w-0on the parent.flex-noneon the footer is not optional; without it a long user name can compress the row to nothing.[overscroll-behavior:contain]belongs on the scrolling nav, not the rail.- A 60vh rail on a phone is a compromise, not a pattern — if the nav is genuinely long on mobile, use demo 19's drawer.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
Flexbox is Baseline 2017 and this layout has no modern requirement at all; scrollbar-gutter (Safari 17.4) is the only progressive part and degrades to a slight shift. Tailwind v4's generated sheet is the binding floor.