11 / 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 Collapsible Sidebar with Checkbox State
The zero-JS collapse. A visually hidden checkbox holds the state, a <label> is the control, and every responding element reads the state through group-has-checked: — which is precisely what the :has() era gave utility CSS. Compare demo 05, the scripted version: same product, and the honest trade is that a checkbox cannot carry aria-expanded.
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-11 block relative w-full min-h-svh bg-tsb11-bg text-tsb11-ink font-tsb11-sans leading-[1.55]" aria-label="Vale account settings">
<div class="absolute top-3.5 right-3.5 z-60">
<input class="tsb-11-theme-input peer absolute inset-0 w-full h-full m-0 opacity-0 cursor-pointer" type="checkbox" id="tsb-11-theme">
<label class="grid place-items-center w-11 h-11 rounded-[1.4rem] border-0 bg-tsb11-surf2 text-tsb11-ink cursor-pointer peer-focus-visible:outline-2 peer-focus-visible:outline-tsb11-acc peer-focus-visible:outline-offset-2" for="tsb-11-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="group flex min-w-0 min-h-svh bg-tsb11-surf max-r30:flex-col">
<input class="absolute w-px h-px m-0 overflow-hidden whitespace-nowrap [clip-path:inset(50%)]" type="checkbox" id="tsb-11-collapse">
<aside class="flex-none flex flex-col min-w-0 w-[var(--tsb11-w-open)] bg-tsb11-surf2 transition-[width] duration-200 ease-out group-has-checked:w-[var(--tsb11-w-rail)] motion-reduce:transition-none max-r30:w-full max-r30:group-has-checked:w-full">
<div class="p-2.5 pr-14 r30:pr-2.5">
<label class="flex items-center gap-2.5 min-w-11 min-h-11 px-2.5 rounded-[1.4rem] bg-tsb11-surf text-[0.86rem] font-semibold whitespace-nowrap cursor-pointer [&>svg]:w-4.5 [&>svg]:h-4.5 [&>svg]:flex-none group-has-checked:justify-center group-has-checked:px-0 group-has-[input:focus-visible]:outline-2 group-has-[input:focus-visible]:outline-tsb11-acc group-has-[input:focus-visible]:outline-offset-2" for="tsb-11-collapse">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M4 6h16M4 12h16M4 18h16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>
<span class="group-has-checked:absolute group-has-checked:w-px group-has-checked:h-px group-has-checked:overflow-hidden group-has-checked:whitespace-nowrap group-has-checked:[clip-path:inset(50%)]">Collapse</span>
<span class="hidden group-has-checked:block group-has-checked:absolute group-has-checked:w-px group-has-checked:h-px group-has-checked:overflow-hidden group-has-checked:whitespace-nowrap group-has-checked:[clip-path:inset(50%)]">Expand</span>
</label>
</div>
<nav class="flex-1 min-h-0 px-2.5 py-1" aria-label="Vale account settings">
<ul class="list-none m-0 p-0 grid gap-0.5">
<li><a class="flex items-center gap-3 min-w-0 min-h-11 px-2.5 py-2 rounded-[1.4rem] text-[0.92rem] text-tsb11-ink no-underline whitespace-nowrap hover:bg-tsb11-surf focus-visible:outline-2 focus-visible:outline-tsb11-acc focus-visible:outline-offset-2 aria-[current=page]:bg-tsb11-acc aria-[current=page]:text-tsb11-onacc aria-[current=page]:font-semibold aria-[current=page]:[&>span:first-child]:text-current group-has-checked:justify-center group-has-checked:gap-0" href="#tsb-11-main" aria-current="page"><span class="flex-none text-[0.6rem] text-tsb11-mut" aria-hidden="true">●</span><span class="min-w-0 overflow-hidden text-ellipsis group-has-checked:absolute group-has-checked:w-px group-has-checked:h-px group-has-checked:whitespace-nowrap group-has-checked:[clip-path:inset(50%)]">Profile</span></a></li>
<li><a class="flex items-center gap-3 min-w-0 min-h-11 px-2.5 py-2 rounded-[1.4rem] text-[0.92rem] text-tsb11-ink no-underline whitespace-nowrap hover:bg-tsb11-surf focus-visible:outline-2 focus-visible:outline-tsb11-acc focus-visible:outline-offset-2 group-has-checked:justify-center group-has-checked:gap-0" href="#tsb-11-main"><span class="flex-none text-[0.6rem] text-tsb11-mut" aria-hidden="true">●</span><span class="min-w-0 overflow-hidden text-ellipsis group-has-checked:absolute group-has-checked:w-px group-has-checked:h-px group-has-checked:whitespace-nowrap group-has-checked:[clip-path:inset(50%)]">Notifications</span></a></li>
<li><a class="flex items-center gap-3 min-w-0 min-h-11 px-2.5 py-2 rounded-[1.4rem] text-[0.92rem] text-tsb11-ink no-underline whitespace-nowrap hover:bg-tsb11-surf focus-visible:outline-2 focus-visible:outline-tsb11-acc focus-visible:outline-offset-2 group-has-checked:justify-center group-has-checked:gap-0" href="#tsb-11-main"><span class="flex-none text-[0.6rem] text-tsb11-mut" aria-hidden="true">●</span><span class="min-w-0 overflow-hidden text-ellipsis group-has-checked:absolute group-has-checked:w-px group-has-checked:h-px group-has-checked:whitespace-nowrap group-has-checked:[clip-path:inset(50%)]">Security</span></a></li>
<li><a class="flex items-center gap-3 min-w-0 min-h-11 px-2.5 py-2 rounded-[1.4rem] text-[0.92rem] text-tsb11-ink no-underline whitespace-nowrap hover:bg-tsb11-surf focus-visible:outline-2 focus-visible:outline-tsb11-acc focus-visible:outline-offset-2 group-has-checked:justify-center group-has-checked:gap-0" href="#tsb-11-main"><span class="flex-none text-[0.6rem] text-tsb11-mut" aria-hidden="true">●</span><span class="min-w-0 overflow-hidden text-ellipsis group-has-checked:absolute group-has-checked:w-px group-has-checked:h-px group-has-checked:whitespace-nowrap group-has-checked:[clip-path:inset(50%)]">Billing</span></a></li>
<li><a class="flex items-center gap-3 min-w-0 min-h-11 px-2.5 py-2 rounded-[1.4rem] text-[0.92rem] text-tsb11-ink no-underline whitespace-nowrap hover:bg-tsb11-surf focus-visible:outline-2 focus-visible:outline-tsb11-acc focus-visible:outline-offset-2 group-has-checked:justify-center group-has-checked:gap-0" href="#tsb-11-main"><span class="flex-none text-[0.6rem] text-tsb11-mut" aria-hidden="true">●</span><span class="min-w-0 overflow-hidden text-ellipsis group-has-checked:absolute group-has-checked:w-px group-has-checked:h-px group-has-checked:whitespace-nowrap group-has-checked:[clip-path:inset(50%)]">API keys</span></a></li>
</ul>
</nav>
<p class="m-0 px-4 pt-2.5 pb-3.5 overflow-hidden whitespace-nowrap text-[0.78rem] text-tsb11-mut"><span class="min-w-0 overflow-hidden text-ellipsis group-has-checked:absolute group-has-checked:w-px group-has-checked:h-px group-has-checked:whitespace-nowrap group-has-checked:[clip-path:inset(50%)]">Signed in as Priya Sharma</span></p>
</aside>
<main class="flex-1 min-w-0 px-5 pt-6 pb-10 r46:px-9 r46:pt-10 r46:pb-12" id="tsb-11-main">
<h2 class="m-0 mb-5 font-tsb11-display text-[clamp(1.35rem,3.4vw,1.9rem)] leading-[1.15]">Profile</h2>
<dl class="m-0 grid grid-cols-[minmax(0,1fr)] gap-2.5 max-w-[44rem] r34:grid-cols-2 [&>div]:min-w-0 [&>div]:px-3.5 [&>div]:py-3 [&>div]:rounded-[0.9rem] [&>div]:bg-tsb11-surf2 [&_dt]:text-[0.72rem] [&_dt]:tracking-[0.08em] [&_dt]:uppercase [&_dt]:text-tsb11-mut [&_dd]:mt-0.5 [&_dd]:ml-0 [&_dd]:text-[0.92rem]">
<div><dt>Display name</dt><dd>Priya Sharma</dd></div>
<div><dt>Workspace</dt><dd>Vale · Operations</dd></div>
<div><dt>Time zone</dt><dd>Europe/Lisbon (UTC+1)</dd></div>
<div><dt>Two-factor</dt><dd>Authenticator app</dd></div>
<div><dt>Recovery email</dt><dd>p.sharma@vale.example</dd></div>
<div><dt>Member since</dt><dd>March 2024</dd></div>
</dl>
</main>
</div>
</section><section class="tsb-11 block relative w-full min-h-svh bg-tsb11-bg text-tsb11-ink font-tsb11-sans leading-[1.55]" aria-label="Vale account settings">
<div class="absolute top-3.5 right-3.5 z-60">
<input class="tsb-11-theme-input peer absolute inset-0 w-full h-full m-0 opacity-0 cursor-pointer" type="checkbox" id="tsb-11-theme">
<label class="grid place-items-center w-11 h-11 rounded-[1.4rem] border-0 bg-tsb11-surf2 text-tsb11-ink cursor-pointer peer-focus-visible:outline-2 peer-focus-visible:outline-tsb11-acc peer-focus-visible:outline-offset-2" for="tsb-11-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="group flex min-w-0 min-h-svh bg-tsb11-surf max-r30:flex-col">
<input class="absolute w-px h-px m-0 overflow-hidden whitespace-nowrap [clip-path:inset(50%)]" type="checkbox" id="tsb-11-collapse">
<aside class="flex-none flex flex-col min-w-0 w-[var(--tsb11-w-open)] bg-tsb11-surf2 transition-[width] duration-200 ease-out group-has-checked:w-[var(--tsb11-w-rail)] motion-reduce:transition-none max-r30:w-full max-r30:group-has-checked:w-full">
<div class="p-2.5 pr-14 r30:pr-2.5">
<label class="flex items-center gap-2.5 min-w-11 min-h-11 px-2.5 rounded-[1.4rem] bg-tsb11-surf text-[0.86rem] font-semibold whitespace-nowrap cursor-pointer [&>svg]:w-4.5 [&>svg]:h-4.5 [&>svg]:flex-none group-has-checked:justify-center group-has-checked:px-0 group-has-[input:focus-visible]:outline-2 group-has-[input:focus-visible]:outline-tsb11-acc group-has-[input:focus-visible]:outline-offset-2" for="tsb-11-collapse">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M4 6h16M4 12h16M4 18h16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>
<span class="group-has-checked:absolute group-has-checked:w-px group-has-checked:h-px group-has-checked:overflow-hidden group-has-checked:whitespace-nowrap group-has-checked:[clip-path:inset(50%)]">Collapse</span>
<span class="hidden group-has-checked:block group-has-checked:absolute group-has-checked:w-px group-has-checked:h-px group-has-checked:overflow-hidden group-has-checked:whitespace-nowrap group-has-checked:[clip-path:inset(50%)]">Expand</span>
</label>
</div>
<nav class="flex-1 min-h-0 px-2.5 py-1" aria-label="Vale account settings">
<ul class="list-none m-0 p-0 grid gap-0.5">
<li><a class="flex items-center gap-3 min-w-0 min-h-11 px-2.5 py-2 rounded-[1.4rem] text-[0.92rem] text-tsb11-ink no-underline whitespace-nowrap hover:bg-tsb11-surf focus-visible:outline-2 focus-visible:outline-tsb11-acc focus-visible:outline-offset-2 aria-[current=page]:bg-tsb11-acc aria-[current=page]:text-tsb11-onacc aria-[current=page]:font-semibold aria-[current=page]:[&>span:first-child]:text-current group-has-checked:justify-center group-has-checked:gap-0" href="#tsb-11-main" aria-current="page"><span class="flex-none text-[0.6rem] text-tsb11-mut" aria-hidden="true">●</span><span class="min-w-0 overflow-hidden text-ellipsis group-has-checked:absolute group-has-checked:w-px group-has-checked:h-px group-has-checked:whitespace-nowrap group-has-checked:[clip-path:inset(50%)]">Profile</span></a></li>
<li><a class="flex items-center gap-3 min-w-0 min-h-11 px-2.5 py-2 rounded-[1.4rem] text-[0.92rem] text-tsb11-ink no-underline whitespace-nowrap hover:bg-tsb11-surf focus-visible:outline-2 focus-visible:outline-tsb11-acc focus-visible:outline-offset-2 group-has-checked:justify-center group-has-checked:gap-0" href="#tsb-11-main"><span class="flex-none text-[0.6rem] text-tsb11-mut" aria-hidden="true">●</span><span class="min-w-0 overflow-hidden text-ellipsis group-has-checked:absolute group-has-checked:w-px group-has-checked:h-px group-has-checked:whitespace-nowrap group-has-checked:[clip-path:inset(50%)]">Notifications</span></a></li>
<li><a class="flex items-center gap-3 min-w-0 min-h-11 px-2.5 py-2 rounded-[1.4rem] text-[0.92rem] text-tsb11-ink no-underline whitespace-nowrap hover:bg-tsb11-surf focus-visible:outline-2 focus-visible:outline-tsb11-acc focus-visible:outline-offset-2 group-has-checked:justify-center group-has-checked:gap-0" href="#tsb-11-main"><span class="flex-none text-[0.6rem] text-tsb11-mut" aria-hidden="true">●</span><span class="min-w-0 overflow-hidden text-ellipsis group-has-checked:absolute group-has-checked:w-px group-has-checked:h-px group-has-checked:whitespace-nowrap group-has-checked:[clip-path:inset(50%)]">Security</span></a></li>
<li><a class="flex items-center gap-3 min-w-0 min-h-11 px-2.5 py-2 rounded-[1.4rem] text-[0.92rem] text-tsb11-ink no-underline whitespace-nowrap hover:bg-tsb11-surf focus-visible:outline-2 focus-visible:outline-tsb11-acc focus-visible:outline-offset-2 group-has-checked:justify-center group-has-checked:gap-0" href="#tsb-11-main"><span class="flex-none text-[0.6rem] text-tsb11-mut" aria-hidden="true">●</span><span class="min-w-0 overflow-hidden text-ellipsis group-has-checked:absolute group-has-checked:w-px group-has-checked:h-px group-has-checked:whitespace-nowrap group-has-checked:[clip-path:inset(50%)]">Billing</span></a></li>
<li><a class="flex items-center gap-3 min-w-0 min-h-11 px-2.5 py-2 rounded-[1.4rem] text-[0.92rem] text-tsb11-ink no-underline whitespace-nowrap hover:bg-tsb11-surf focus-visible:outline-2 focus-visible:outline-tsb11-acc focus-visible:outline-offset-2 group-has-checked:justify-center group-has-checked:gap-0" href="#tsb-11-main"><span class="flex-none text-[0.6rem] text-tsb11-mut" aria-hidden="true">●</span><span class="min-w-0 overflow-hidden text-ellipsis group-has-checked:absolute group-has-checked:w-px group-has-checked:h-px group-has-checked:whitespace-nowrap group-has-checked:[clip-path:inset(50%)]">API keys</span></a></li>
</ul>
</nav>
<p class="m-0 px-4 pt-2.5 pb-3.5 overflow-hidden whitespace-nowrap text-[0.78rem] text-tsb11-mut"><span class="min-w-0 overflow-hidden text-ellipsis group-has-checked:absolute group-has-checked:w-px group-has-checked:h-px group-has-checked:whitespace-nowrap group-has-checked:[clip-path:inset(50%)]">Signed in as Priya Sharma</span></p>
</aside>
<main class="flex-1 min-w-0 px-5 pt-6 pb-10 r46:px-9 r46:pt-10 r46:pb-12" id="tsb-11-main">
<h2 class="m-0 mb-5 font-tsb11-display text-[clamp(1.35rem,3.4vw,1.9rem)] leading-[1.15]">Profile</h2>
<dl class="m-0 grid grid-cols-[minmax(0,1fr)] gap-2.5 max-w-[44rem] r34:grid-cols-2 [&>div]:min-w-0 [&>div]:px-3.5 [&>div]:py-3 [&>div]:rounded-[0.9rem] [&>div]:bg-tsb11-surf2 [&_dt]:text-[0.72rem] [&_dt]:tracking-[0.08em] [&_dt]:uppercase [&_dt]:text-tsb11-mut [&_dd]:mt-0.5 [&_dd]:ml-0 [&_dd]:text-[0.92rem]">
<div><dt>Display name</dt><dd>Priya Sharma</dd></div>
<div><dt>Workspace</dt><dd>Vale · Operations</dd></div>
<div><dt>Time zone</dt><dd>Europe/Lisbon (UTC+1)</dd></div>
<div><dt>Two-factor</dt><dd>Authenticator app</dd></div>
<div><dt>Recovery email</dt><dd>p.sharma@vale.example</dd></div>
<div><dt>Member since</dt><dd>March 2024</dd></div>
</dl>
</main>
</div>
</section>@import "tailwindcss";
@theme {
--color-tsb11-bg: #f5f1ee;
--color-tsb11-bg: oklch(95.5% 0.008 45);
--color-tsb11-surf: #fffcfa;
--color-tsb11-surf: oklch(99% 0.004 45);
--color-tsb11-surf2: #ebe3dd;
--color-tsb11-surf2: oklch(92% 0.014 45);
--color-tsb11-line: #d9cdc4;
--color-tsb11-line: oklch(85% 0.02 45);
--color-tsb11-ink: #241c17;
--color-tsb11-ink: oklch(24% 0.018 45);
--color-tsb11-mut: #6d5c52;
--color-tsb11-mut: oklch(48% 0.025 45);
--color-tsb11-acc: #7d4a35;
--color-tsb11-acc: oklch(46% 0.09 40);
--color-tsb11-accsoft: #eee0d8;
--color-tsb11-accsoft: oklch(92% 0.02 45);
--color-tsb11-onacc: #fffcfa;
--color-tsb11-onacc: oklch(99% 0.004 45);
--font-tsb11-sans: system-ui, -apple-system, 'Segoe UI', sans-serif;
--font-tsb11-display: 'Quicksand', Nunito, ui-rounded, system-ui, sans-serif;
--breakpoint-r30: 30rem;
--breakpoint-r34: 34rem;
--breakpoint-r46: 46rem;
}
/* Root rule: the full-bleed contract plus the two rail widths. */
.tsb-11 {
width: 100%;
min-height: 100svh;
--tsb11-w-open: 15rem;
--tsb11-w-rail: 4.5rem;
}
.tsb-11 *,
.tsb-11 *::before,
.tsb-11 *::after {
box-sizing: border-box;
}
/* Token flip for the pure-CSS theme toggle, plus the two mandatory mirror blocks.
Note this is a SECOND checkbox in the demo, independent of the collapse one — which is
why the collapse group is scoped to the app shell, not to the section root. */
.tsb-11:has(.tsb-11-theme-input:checked) {
--color-tsb11-bg: #14100e;
--color-tsb11-surf: #1c1714;
--color-tsb11-surf2: #251e1a;
--color-tsb11-line: #362c26;
--color-tsb11-ink: #f1e9e3;
--color-tsb11-mut: #a89689;
--color-tsb11-acc: #dda183;
--color-tsb11-acc: oklch(78% 0.09 45);
--color-tsb11-accsoft: #32241d;
--color-tsb11-onacc: #1c1714;
}
@media (prefers-color-scheme: dark) {
.tsb-11 {
--color-tsb11-bg: #14100e;
--color-tsb11-surf: #1c1714;
--color-tsb11-surf2: #251e1a;
--color-tsb11-line: #362c26;
--color-tsb11-ink: #f1e9e3;
--color-tsb11-mut: #a89689;
--color-tsb11-acc: #dda183;
--color-tsb11-acc: oklch(78% 0.09 45);
--color-tsb11-accsoft: #32241d;
--color-tsb11-onacc: #1c1714;
}
.tsb-11:has(.tsb-11-theme-input:checked) {
--color-tsb11-bg: #f5f1ee;
--color-tsb11-surf: #fffcfa;
--color-tsb11-surf2: #ebe3dd;
--color-tsb11-line: #d9cdc4;
--color-tsb11-ink: #241c17;
--color-tsb11-mut: #6d5c52;
--color-tsb11-acc: #7d4a35;
--color-tsb11-acc: oklch(46% 0.09 40);
--color-tsb11-accsoft: #eee0d8;
--color-tsb11-onacc: #fffcfa;
}
}
@media (prefers-color-scheme: light) {
.tsb-11:has(.tsb-11-theme-input:checked) {
--color-tsb11-bg: #14100e;
--color-tsb11-surf: #1c1714;
--color-tsb11-surf2: #251e1a;
--color-tsb11-line: #362c26;
--color-tsb11-ink: #f1e9e3;
--color-tsb11-mut: #a89689;
--color-tsb11-acc: #dda183;
--color-tsb11-acc: oklch(78% 0.09 45);
--color-tsb11-accsoft: #32241d;
--color-tsb11-onacc: #1c1714;
}
}@import "tailwindcss";
@theme {
--color-tsb11-bg: #f5f1ee;
--color-tsb11-bg: oklch(95.5% 0.008 45);
--color-tsb11-surf: #fffcfa;
--color-tsb11-surf: oklch(99% 0.004 45);
--color-tsb11-surf2: #ebe3dd;
--color-tsb11-surf2: oklch(92% 0.014 45);
--color-tsb11-line: #d9cdc4;
--color-tsb11-line: oklch(85% 0.02 45);
--color-tsb11-ink: #241c17;
--color-tsb11-ink: oklch(24% 0.018 45);
--color-tsb11-mut: #6d5c52;
--color-tsb11-mut: oklch(48% 0.025 45);
--color-tsb11-acc: #7d4a35;
--color-tsb11-acc: oklch(46% 0.09 40);
--color-tsb11-accsoft: #eee0d8;
--color-tsb11-accsoft: oklch(92% 0.02 45);
--color-tsb11-onacc: #fffcfa;
--color-tsb11-onacc: oklch(99% 0.004 45);
--font-tsb11-sans: system-ui, -apple-system, 'Segoe UI', sans-serif;
--font-tsb11-display: 'Quicksand', Nunito, ui-rounded, system-ui, sans-serif;
--breakpoint-r30: 30rem;
--breakpoint-r34: 34rem;
--breakpoint-r46: 46rem;
}
/* Root rule: the full-bleed contract plus the two rail widths. */
.tsb-11 {
width: 100%;
min-height: 100svh;
--tsb11-w-open: 15rem;
--tsb11-w-rail: 4.5rem;
}
.tsb-11 *,
.tsb-11 *::before,
.tsb-11 *::after {
box-sizing: border-box;
}
/* Token flip for the pure-CSS theme toggle, plus the two mandatory mirror blocks.
Note this is a SECOND checkbox in the demo, independent of the collapse one — which is
why the collapse group is scoped to the app shell, not to the section root. */
.tsb-11:has(.tsb-11-theme-input:checked) {
--color-tsb11-bg: #14100e;
--color-tsb11-surf: #1c1714;
--color-tsb11-surf2: #251e1a;
--color-tsb11-line: #362c26;
--color-tsb11-ink: #f1e9e3;
--color-tsb11-mut: #a89689;
--color-tsb11-acc: #dda183;
--color-tsb11-acc: oklch(78% 0.09 45);
--color-tsb11-accsoft: #32241d;
--color-tsb11-onacc: #1c1714;
}
@media (prefers-color-scheme: dark) {
.tsb-11 {
--color-tsb11-bg: #14100e;
--color-tsb11-surf: #1c1714;
--color-tsb11-surf2: #251e1a;
--color-tsb11-line: #362c26;
--color-tsb11-ink: #f1e9e3;
--color-tsb11-mut: #a89689;
--color-tsb11-acc: #dda183;
--color-tsb11-acc: oklch(78% 0.09 45);
--color-tsb11-accsoft: #32241d;
--color-tsb11-onacc: #1c1714;
}
.tsb-11:has(.tsb-11-theme-input:checked) {
--color-tsb11-bg: #f5f1ee;
--color-tsb11-surf: #fffcfa;
--color-tsb11-surf2: #ebe3dd;
--color-tsb11-line: #d9cdc4;
--color-tsb11-ink: #241c17;
--color-tsb11-mut: #6d5c52;
--color-tsb11-acc: #7d4a35;
--color-tsb11-acc: oklch(46% 0.09 40);
--color-tsb11-accsoft: #eee0d8;
--color-tsb11-onacc: #fffcfa;
}
}
@media (prefers-color-scheme: light) {
.tsb-11:has(.tsb-11-theme-input:checked) {
--color-tsb11-bg: #14100e;
--color-tsb11-surf: #1c1714;
--color-tsb11-surf2: #251e1a;
--color-tsb11-line: #362c26;
--color-tsb11-ink: #f1e9e3;
--color-tsb11-mut: #a89689;
--color-tsb11-acc: #dda183;
--color-tsb11-acc: oklch(78% 0.09 45);
--color-tsb11-accsoft: #32241d;
--color-tsb11-onacc: #1c1714;
}
}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-tsb11-bg: #f5f1ee;
--color-tsb11-bg: oklch(95.5% 0.008 45);
--color-tsb11-surf: #fffcfa;
--color-tsb11-surf: oklch(99% 0.004 45);
--color-tsb11-surf2: #ebe3dd;
--color-tsb11-surf2: oklch(92% 0.014 45);
--color-tsb11-line: #d9cdc4;
--color-tsb11-line: oklch(85% 0.02 45);
--color-tsb11-ink: #241c17;
--color-tsb11-ink: oklch(24% 0.018 45);
--color-tsb11-mut: #6d5c52;
--color-tsb11-mut: oklch(48% 0.025 45);
--color-tsb11-acc: #7d4a35;
--color-tsb11-acc: oklch(46% 0.09 40);
--color-tsb11-accsoft: #eee0d8;
--color-tsb11-accsoft: oklch(92% 0.02 45);
--color-tsb11-onacc: #fffcfa;
--color-tsb11-onacc: oklch(99% 0.004 45);
--font-tsb11-sans: system-ui, -apple-system, 'Segoe UI', sans-serif;
--font-tsb11-display: 'Quicksand', Nunito, ui-rounded, system-ui, sans-serif;
--breakpoint-r30: 30rem;
--breakpoint-r34: 34rem;
--breakpoint-r46: 46rem;
}
/* Root rule: the full-bleed contract plus the two rail widths. */
.tsb-11 {
width: 100%;
min-height: 100svh;
--tsb11-w-open: 15rem;
--tsb11-w-rail: 4.5rem;
}
.tsb-11 *,
.tsb-11 *::before,
.tsb-11 *::after {
box-sizing: border-box;
}
/* Token flip for the pure-CSS theme toggle, plus the two mandatory mirror blocks.
Note this is a SECOND checkbox in the demo, independent of the collapse one — which is
why the collapse group is scoped to the app shell, not to the section root. */
.tsb-11:has(.tsb-11-theme-input:checked) {
--color-tsb11-bg: #14100e;
--color-tsb11-surf: #1c1714;
--color-tsb11-surf2: #251e1a;
--color-tsb11-line: #362c26;
--color-tsb11-ink: #f1e9e3;
--color-tsb11-mut: #a89689;
--color-tsb11-acc: #dda183;
--color-tsb11-acc: oklch(78% 0.09 45);
--color-tsb11-accsoft: #32241d;
--color-tsb11-onacc: #1c1714;
}
@media (prefers-color-scheme: dark) {
.tsb-11 {
--color-tsb11-bg: #14100e;
--color-tsb11-surf: #1c1714;
--color-tsb11-surf2: #251e1a;
--color-tsb11-line: #362c26;
--color-tsb11-ink: #f1e9e3;
--color-tsb11-mut: #a89689;
--color-tsb11-acc: #dda183;
--color-tsb11-acc: oklch(78% 0.09 45);
--color-tsb11-accsoft: #32241d;
--color-tsb11-onacc: #1c1714;
}
.tsb-11:has(.tsb-11-theme-input:checked) {
--color-tsb11-bg: #f5f1ee;
--color-tsb11-surf: #fffcfa;
--color-tsb11-surf2: #ebe3dd;
--color-tsb11-line: #d9cdc4;
--color-tsb11-ink: #241c17;
--color-tsb11-mut: #6d5c52;
--color-tsb11-acc: #7d4a35;
--color-tsb11-acc: oklch(46% 0.09 40);
--color-tsb11-accsoft: #eee0d8;
--color-tsb11-onacc: #fffcfa;
}
}
@media (prefers-color-scheme: light) {
.tsb-11:has(.tsb-11-theme-input:checked) {
--color-tsb11-bg: #14100e;
--color-tsb11-surf: #1c1714;
--color-tsb11-surf2: #251e1a;
--color-tsb11-line: #362c26;
--color-tsb11-ink: #f1e9e3;
--color-tsb11-mut: #a89689;
--color-tsb11-acc: #dda183;
--color-tsb11-acc: oklch(78% 0.09 45);
--color-tsb11-accsoft: #32241d;
--color-tsb11-onacc: #1c1714;
}
}@import "tailwindcss";
@theme {
--color-tsb11-bg: #f5f1ee;
--color-tsb11-bg: oklch(95.5% 0.008 45);
--color-tsb11-surf: #fffcfa;
--color-tsb11-surf: oklch(99% 0.004 45);
--color-tsb11-surf2: #ebe3dd;
--color-tsb11-surf2: oklch(92% 0.014 45);
--color-tsb11-line: #d9cdc4;
--color-tsb11-line: oklch(85% 0.02 45);
--color-tsb11-ink: #241c17;
--color-tsb11-ink: oklch(24% 0.018 45);
--color-tsb11-mut: #6d5c52;
--color-tsb11-mut: oklch(48% 0.025 45);
--color-tsb11-acc: #7d4a35;
--color-tsb11-acc: oklch(46% 0.09 40);
--color-tsb11-accsoft: #eee0d8;
--color-tsb11-accsoft: oklch(92% 0.02 45);
--color-tsb11-onacc: #fffcfa;
--color-tsb11-onacc: oklch(99% 0.004 45);
--font-tsb11-sans: system-ui, -apple-system, 'Segoe UI', sans-serif;
--font-tsb11-display: 'Quicksand', Nunito, ui-rounded, system-ui, sans-serif;
--breakpoint-r30: 30rem;
--breakpoint-r34: 34rem;
--breakpoint-r46: 46rem;
}
/* Root rule: the full-bleed contract plus the two rail widths. */
.tsb-11 {
width: 100%;
min-height: 100svh;
--tsb11-w-open: 15rem;
--tsb11-w-rail: 4.5rem;
}
.tsb-11 *,
.tsb-11 *::before,
.tsb-11 *::after {
box-sizing: border-box;
}
/* Token flip for the pure-CSS theme toggle, plus the two mandatory mirror blocks.
Note this is a SECOND checkbox in the demo, independent of the collapse one — which is
why the collapse group is scoped to the app shell, not to the section root. */
.tsb-11:has(.tsb-11-theme-input:checked) {
--color-tsb11-bg: #14100e;
--color-tsb11-surf: #1c1714;
--color-tsb11-surf2: #251e1a;
--color-tsb11-line: #362c26;
--color-tsb11-ink: #f1e9e3;
--color-tsb11-mut: #a89689;
--color-tsb11-acc: #dda183;
--color-tsb11-acc: oklch(78% 0.09 45);
--color-tsb11-accsoft: #32241d;
--color-tsb11-onacc: #1c1714;
}
@media (prefers-color-scheme: dark) {
.tsb-11 {
--color-tsb11-bg: #14100e;
--color-tsb11-surf: #1c1714;
--color-tsb11-surf2: #251e1a;
--color-tsb11-line: #362c26;
--color-tsb11-ink: #f1e9e3;
--color-tsb11-mut: #a89689;
--color-tsb11-acc: #dda183;
--color-tsb11-acc: oklch(78% 0.09 45);
--color-tsb11-accsoft: #32241d;
--color-tsb11-onacc: #1c1714;
}
.tsb-11:has(.tsb-11-theme-input:checked) {
--color-tsb11-bg: #f5f1ee;
--color-tsb11-surf: #fffcfa;
--color-tsb11-surf2: #ebe3dd;
--color-tsb11-line: #d9cdc4;
--color-tsb11-ink: #241c17;
--color-tsb11-mut: #6d5c52;
--color-tsb11-acc: #7d4a35;
--color-tsb11-acc: oklch(46% 0.09 40);
--color-tsb11-accsoft: #eee0d8;
--color-tsb11-onacc: #fffcfa;
}
}
@media (prefers-color-scheme: light) {
.tsb-11:has(.tsb-11-theme-input:checked) {
--color-tsb11-bg: #14100e;
--color-tsb11-surf: #1c1714;
--color-tsb11-surf2: #251e1a;
--color-tsb11-line: #362c26;
--color-tsb11-ink: #f1e9e3;
--color-tsb11-mut: #a89689;
--color-tsb11-acc: #dda183;
--color-tsb11-acc: oklch(78% 0.09 45);
--color-tsb11-accsoft: #32241d;
--color-tsb11-onacc: #1c1714;
}
}<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=Quicksand:wght@400;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=Quicksand:wght@400;600;700&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
- Six
:has(.state:checked)rules becomegroup-has-checked:utilities on the rail, the labels, the toggle captions, the links and the footer. This is the collection's cleanest demonstration of what:has()did for utility CSS. peer-checked:would not work here: it only reaches later siblings, so it would dictate markup order.group-has-checked:on a wrapper frees the input to sit anywhere.- The
groupis on the app shell rather than the section root deliberately — the theme toggle is a second checkbox, and a root-level group would make everygroup-has-checked:utility respond to it too. - Focus is on the hidden input, so the label's ring is
group-has-[input:focus-visible]:outline-2— a scoped:has()that reads the state of a control it is not a sibling of. - The caption swap is two spans with complementary variants, which is the utility equivalent of the CSS version's two
displayrules, and it keeps both strings in the markup where they can be translated. - Everything else — widths, transition, reduced motion — is identical in structure to demo 05's scripted version, which is the point of shipping both.
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 Collapsible Sidebar with Checkbox State
Source: https://codefronts.com/navigation/tailwind-css-sidebars/pure-tailwind-collapsible-sidebar-with-checkbox-state/
The zero-JS collapse. A visually hidden checkbox holds the state, a <label> is the control, and every responding element reads the state through group-has-checked: — which is precisely what the :has() era gave utility CSS. Compare demo 05, the scripted version: same product, and the honest trade is that a checkbox cannot carry aria-expanded.
## HTML
```html
<section class="tsb-11 block relative w-full min-h-svh bg-tsb11-bg text-tsb11-ink font-tsb11-sans leading-[1.55]" aria-label="Vale account settings">
<div class="absolute top-3.5 right-3.5 z-60">
<input class="tsb-11-theme-input peer absolute inset-0 w-full h-full m-0 opacity-0 cursor-pointer" type="checkbox" id="tsb-11-theme">
<label class="grid place-items-center w-11 h-11 rounded-[1.4rem] border-0 bg-tsb11-surf2 text-tsb11-ink cursor-pointer peer-focus-visible:outline-2 peer-focus-visible:outline-tsb11-acc peer-focus-visible:outline-offset-2" for="tsb-11-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="group flex min-w-0 min-h-svh bg-tsb11-surf max-r30:flex-col">
<input class="absolute w-px h-px m-0 overflow-hidden whitespace-nowrap [clip-path:inset(50%)]" type="checkbox" id="tsb-11-collapse">
<aside class="flex-none flex flex-col min-w-0 w-[var(--tsb11-w-open)] bg-tsb11-surf2 transition-[width] duration-200 ease-out group-has-checked:w-[var(--tsb11-w-rail)] motion-reduce:transition-none max-r30:w-full max-r30:group-has-checked:w-full">
<div class="p-2.5 pr-14 r30:pr-2.5">
<label class="flex items-center gap-2.5 min-w-11 min-h-11 px-2.5 rounded-[1.4rem] bg-tsb11-surf text-[0.86rem] font-semibold whitespace-nowrap cursor-pointer [&>svg]:w-4.5 [&>svg]:h-4.5 [&>svg]:flex-none group-has-checked:justify-center group-has-checked:px-0 group-has-[input:focus-visible]:outline-2 group-has-[input:focus-visible]:outline-tsb11-acc group-has-[input:focus-visible]:outline-offset-2" for="tsb-11-collapse">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M4 6h16M4 12h16M4 18h16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>
<span class="group-has-checked:absolute group-has-checked:w-px group-has-checked:h-px group-has-checked:overflow-hidden group-has-checked:whitespace-nowrap group-has-checked:[clip-path:inset(50%)]">Collapse</span>
<span class="hidden group-has-checked:block group-has-checked:absolute group-has-checked:w-px group-has-checked:h-px group-has-checked:overflow-hidden group-has-checked:whitespace-nowrap group-has-checked:[clip-path:inset(50%)]">Expand</span>
</label>
</div>
<nav class="flex-1 min-h-0 px-2.5 py-1" aria-label="Vale account settings">
<ul class="list-none m-0 p-0 grid gap-0.5">
<li><a class="flex items-center gap-3 min-w-0 min-h-11 px-2.5 py-2 rounded-[1.4rem] text-[0.92rem] text-tsb11-ink no-underline whitespace-nowrap hover:bg-tsb11-surf focus-visible:outline-2 focus-visible:outline-tsb11-acc focus-visible:outline-offset-2 aria-[current=page]:bg-tsb11-acc aria-[current=page]:text-tsb11-onacc aria-[current=page]:font-semibold aria-[current=page]:[&>span:first-child]:text-current group-has-checked:justify-center group-has-checked:gap-0" href="#tsb-11-main" aria-current="page"><span class="flex-none text-[0.6rem] text-tsb11-mut" aria-hidden="true">●</span><span class="min-w-0 overflow-hidden text-ellipsis group-has-checked:absolute group-has-checked:w-px group-has-checked:h-px group-has-checked:whitespace-nowrap group-has-checked:[clip-path:inset(50%)]">Profile</span></a></li>
<li><a class="flex items-center gap-3 min-w-0 min-h-11 px-2.5 py-2 rounded-[1.4rem] text-[0.92rem] text-tsb11-ink no-underline whitespace-nowrap hover:bg-tsb11-surf focus-visible:outline-2 focus-visible:outline-tsb11-acc focus-visible:outline-offset-2 group-has-checked:justify-center group-has-checked:gap-0" href="#tsb-11-main"><span class="flex-none text-[0.6rem] text-tsb11-mut" aria-hidden="true">●</span><span class="min-w-0 overflow-hidden text-ellipsis group-has-checked:absolute group-has-checked:w-px group-has-checked:h-px group-has-checked:whitespace-nowrap group-has-checked:[clip-path:inset(50%)]">Notifications</span></a></li>
<li><a class="flex items-center gap-3 min-w-0 min-h-11 px-2.5 py-2 rounded-[1.4rem] text-[0.92rem] text-tsb11-ink no-underline whitespace-nowrap hover:bg-tsb11-surf focus-visible:outline-2 focus-visible:outline-tsb11-acc focus-visible:outline-offset-2 group-has-checked:justify-center group-has-checked:gap-0" href="#tsb-11-main"><span class="flex-none text-[0.6rem] text-tsb11-mut" aria-hidden="true">●</span><span class="min-w-0 overflow-hidden text-ellipsis group-has-checked:absolute group-has-checked:w-px group-has-checked:h-px group-has-checked:whitespace-nowrap group-has-checked:[clip-path:inset(50%)]">Security</span></a></li>
<li><a class="flex items-center gap-3 min-w-0 min-h-11 px-2.5 py-2 rounded-[1.4rem] text-[0.92rem] text-tsb11-ink no-underline whitespace-nowrap hover:bg-tsb11-surf focus-visible:outline-2 focus-visible:outline-tsb11-acc focus-visible:outline-offset-2 group-has-checked:justify-center group-has-checked:gap-0" href="#tsb-11-main"><span class="flex-none text-[0.6rem] text-tsb11-mut" aria-hidden="true">●</span><span class="min-w-0 overflow-hidden text-ellipsis group-has-checked:absolute group-has-checked:w-px group-has-checked:h-px group-has-checked:whitespace-nowrap group-has-checked:[clip-path:inset(50%)]">Billing</span></a></li>
<li><a class="flex items-center gap-3 min-w-0 min-h-11 px-2.5 py-2 rounded-[1.4rem] text-[0.92rem] text-tsb11-ink no-underline whitespace-nowrap hover:bg-tsb11-surf focus-visible:outline-2 focus-visible:outline-tsb11-acc focus-visible:outline-offset-2 group-has-checked:justify-center group-has-checked:gap-0" href="#tsb-11-main"><span class="flex-none text-[0.6rem] text-tsb11-mut" aria-hidden="true">●</span><span class="min-w-0 overflow-hidden text-ellipsis group-has-checked:absolute group-has-checked:w-px group-has-checked:h-px group-has-checked:whitespace-nowrap group-has-checked:[clip-path:inset(50%)]">API keys</span></a></li>
</ul>
</nav>
<p class="m-0 px-4 pt-2.5 pb-3.5 overflow-hidden whitespace-nowrap text-[0.78rem] text-tsb11-mut"><span class="min-w-0 overflow-hidden text-ellipsis group-has-checked:absolute group-has-checked:w-px group-has-checked:h-px group-has-checked:whitespace-nowrap group-has-checked:[clip-path:inset(50%)]">Signed in as Priya Sharma</span></p>
</aside>
<main class="flex-1 min-w-0 px-5 pt-6 pb-10 r46:px-9 r46:pt-10 r46:pb-12" id="tsb-11-main">
<h2 class="m-0 mb-5 font-tsb11-display text-[clamp(1.35rem,3.4vw,1.9rem)] leading-[1.15]">Profile</h2>
<dl class="m-0 grid grid-cols-[minmax(0,1fr)] gap-2.5 max-w-[44rem] r34:grid-cols-2 [&>div]:min-w-0 [&>div]:px-3.5 [&>div]:py-3 [&>div]:rounded-[0.9rem] [&>div]:bg-tsb11-surf2 [&_dt]:text-[0.72rem] [&_dt]:tracking-[0.08em] [&_dt]:uppercase [&_dt]:text-tsb11-mut [&_dd]:mt-0.5 [&_dd]:ml-0 [&_dd]:text-[0.92rem]">
<div><dt>Display name</dt><dd>Priya Sharma</dd></div>
<div><dt>Workspace</dt><dd>Vale · Operations</dd></div>
<div><dt>Time zone</dt><dd>Europe/Lisbon (UTC+1)</dd></div>
<div><dt>Two-factor</dt><dd>Authenticator app</dd></div>
<div><dt>Recovery email</dt><dd>p.sharma@vale.example</dd></div>
<div><dt>Member since</dt><dd>March 2024</dd></div>
</dl>
</main>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tsb11-bg: #f5f1ee;
--color-tsb11-bg: oklch(95.5% 0.008 45);
--color-tsb11-surf: #fffcfa;
--color-tsb11-surf: oklch(99% 0.004 45);
--color-tsb11-surf2: #ebe3dd;
--color-tsb11-surf2: oklch(92% 0.014 45);
--color-tsb11-line: #d9cdc4;
--color-tsb11-line: oklch(85% 0.02 45);
--color-tsb11-ink: #241c17;
--color-tsb11-ink: oklch(24% 0.018 45);
--color-tsb11-mut: #6d5c52;
--color-tsb11-mut: oklch(48% 0.025 45);
--color-tsb11-acc: #7d4a35;
--color-tsb11-acc: oklch(46% 0.09 40);
--color-tsb11-accsoft: #eee0d8;
--color-tsb11-accsoft: oklch(92% 0.02 45);
--color-tsb11-onacc: #fffcfa;
--color-tsb11-onacc: oklch(99% 0.004 45);
--font-tsb11-sans: system-ui, -apple-system, 'Segoe UI', sans-serif;
--font-tsb11-display: 'Quicksand', Nunito, ui-rounded, system-ui, sans-serif;
--breakpoint-r30: 30rem;
--breakpoint-r34: 34rem;
--breakpoint-r46: 46rem;
}
/* Root rule: the full-bleed contract plus the two rail widths. */
.tsb-11 {
width: 100%;
min-height: 100svh;
--tsb11-w-open: 15rem;
--tsb11-w-rail: 4.5rem;
}
.tsb-11 *,
.tsb-11 *::before,
.tsb-11 *::after {
box-sizing: border-box;
}
/* Token flip for the pure-CSS theme toggle, plus the two mandatory mirror blocks.
Note this is a SECOND checkbox in the demo, independent of the collapse one — which is
why the collapse group is scoped to the app shell, not to the section root. */
.tsb-11:has(.tsb-11-theme-input:checked) {
--color-tsb11-bg: #14100e;
--color-tsb11-surf: #1c1714;
--color-tsb11-surf2: #251e1a;
--color-tsb11-line: #362c26;
--color-tsb11-ink: #f1e9e3;
--color-tsb11-mut: #a89689;
--color-tsb11-acc: #dda183;
--color-tsb11-acc: oklch(78% 0.09 45);
--color-tsb11-accsoft: #32241d;
--color-tsb11-onacc: #1c1714;
}
@media (prefers-color-scheme: dark) {
.tsb-11 {
--color-tsb11-bg: #14100e;
--color-tsb11-surf: #1c1714;
--color-tsb11-surf2: #251e1a;
--color-tsb11-line: #362c26;
--color-tsb11-ink: #f1e9e3;
--color-tsb11-mut: #a89689;
--color-tsb11-acc: #dda183;
--color-tsb11-acc: oklch(78% 0.09 45);
--color-tsb11-accsoft: #32241d;
--color-tsb11-onacc: #1c1714;
}
.tsb-11:has(.tsb-11-theme-input:checked) {
--color-tsb11-bg: #f5f1ee;
--color-tsb11-surf: #fffcfa;
--color-tsb11-surf2: #ebe3dd;
--color-tsb11-line: #d9cdc4;
--color-tsb11-ink: #241c17;
--color-tsb11-mut: #6d5c52;
--color-tsb11-acc: #7d4a35;
--color-tsb11-acc: oklch(46% 0.09 40);
--color-tsb11-accsoft: #eee0d8;
--color-tsb11-onacc: #fffcfa;
}
}
@media (prefers-color-scheme: light) {
.tsb-11:has(.tsb-11-theme-input:checked) {
--color-tsb11-bg: #14100e;
--color-tsb11-surf: #1c1714;
--color-tsb11-surf2: #251e1a;
--color-tsb11-line: #362c26;
--color-tsb11-ink: #f1e9e3;
--color-tsb11-mut: #a89689;
--color-tsb11-acc: #dda183;
--color-tsb11-acc: oklch(78% 0.09 45);
--color-tsb11-accsoft: #32241d;
--color-tsb11-onacc: #1c1714;
}
}
```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 Collapsible Sidebar with Checkbox State
Source: https://codefronts.com/navigation/tailwind-css-sidebars/pure-tailwind-collapsible-sidebar-with-checkbox-state/
The zero-JS collapse. A visually hidden checkbox holds the state, a <label> is the control, and every responding element reads the state through group-has-checked: — which is precisely what the :has() era gave utility CSS. Compare demo 05, the scripted version: same product, and the honest trade is that a checkbox cannot carry aria-expanded.
## HTML
```html
<section class="tsb-11 block relative w-full min-h-svh bg-tsb11-bg text-tsb11-ink font-tsb11-sans leading-[1.55]" aria-label="Vale account settings">
<div class="absolute top-3.5 right-3.5 z-60">
<input class="tsb-11-theme-input peer absolute inset-0 w-full h-full m-0 opacity-0 cursor-pointer" type="checkbox" id="tsb-11-theme">
<label class="grid place-items-center w-11 h-11 rounded-[1.4rem] border-0 bg-tsb11-surf2 text-tsb11-ink cursor-pointer peer-focus-visible:outline-2 peer-focus-visible:outline-tsb11-acc peer-focus-visible:outline-offset-2" for="tsb-11-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="group flex min-w-0 min-h-svh bg-tsb11-surf max-r30:flex-col">
<input class="absolute w-px h-px m-0 overflow-hidden whitespace-nowrap [clip-path:inset(50%)]" type="checkbox" id="tsb-11-collapse">
<aside class="flex-none flex flex-col min-w-0 w-[var(--tsb11-w-open)] bg-tsb11-surf2 transition-[width] duration-200 ease-out group-has-checked:w-[var(--tsb11-w-rail)] motion-reduce:transition-none max-r30:w-full max-r30:group-has-checked:w-full">
<div class="p-2.5 pr-14 r30:pr-2.5">
<label class="flex items-center gap-2.5 min-w-11 min-h-11 px-2.5 rounded-[1.4rem] bg-tsb11-surf text-[0.86rem] font-semibold whitespace-nowrap cursor-pointer [&>svg]:w-4.5 [&>svg]:h-4.5 [&>svg]:flex-none group-has-checked:justify-center group-has-checked:px-0 group-has-[input:focus-visible]:outline-2 group-has-[input:focus-visible]:outline-tsb11-acc group-has-[input:focus-visible]:outline-offset-2" for="tsb-11-collapse">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M4 6h16M4 12h16M4 18h16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>
<span class="group-has-checked:absolute group-has-checked:w-px group-has-checked:h-px group-has-checked:overflow-hidden group-has-checked:whitespace-nowrap group-has-checked:[clip-path:inset(50%)]">Collapse</span>
<span class="hidden group-has-checked:block group-has-checked:absolute group-has-checked:w-px group-has-checked:h-px group-has-checked:overflow-hidden group-has-checked:whitespace-nowrap group-has-checked:[clip-path:inset(50%)]">Expand</span>
</label>
</div>
<nav class="flex-1 min-h-0 px-2.5 py-1" aria-label="Vale account settings">
<ul class="list-none m-0 p-0 grid gap-0.5">
<li><a class="flex items-center gap-3 min-w-0 min-h-11 px-2.5 py-2 rounded-[1.4rem] text-[0.92rem] text-tsb11-ink no-underline whitespace-nowrap hover:bg-tsb11-surf focus-visible:outline-2 focus-visible:outline-tsb11-acc focus-visible:outline-offset-2 aria-[current=page]:bg-tsb11-acc aria-[current=page]:text-tsb11-onacc aria-[current=page]:font-semibold aria-[current=page]:[&>span:first-child]:text-current group-has-checked:justify-center group-has-checked:gap-0" href="#tsb-11-main" aria-current="page"><span class="flex-none text-[0.6rem] text-tsb11-mut" aria-hidden="true">●</span><span class="min-w-0 overflow-hidden text-ellipsis group-has-checked:absolute group-has-checked:w-px group-has-checked:h-px group-has-checked:whitespace-nowrap group-has-checked:[clip-path:inset(50%)]">Profile</span></a></li>
<li><a class="flex items-center gap-3 min-w-0 min-h-11 px-2.5 py-2 rounded-[1.4rem] text-[0.92rem] text-tsb11-ink no-underline whitespace-nowrap hover:bg-tsb11-surf focus-visible:outline-2 focus-visible:outline-tsb11-acc focus-visible:outline-offset-2 group-has-checked:justify-center group-has-checked:gap-0" href="#tsb-11-main"><span class="flex-none text-[0.6rem] text-tsb11-mut" aria-hidden="true">●</span><span class="min-w-0 overflow-hidden text-ellipsis group-has-checked:absolute group-has-checked:w-px group-has-checked:h-px group-has-checked:whitespace-nowrap group-has-checked:[clip-path:inset(50%)]">Notifications</span></a></li>
<li><a class="flex items-center gap-3 min-w-0 min-h-11 px-2.5 py-2 rounded-[1.4rem] text-[0.92rem] text-tsb11-ink no-underline whitespace-nowrap hover:bg-tsb11-surf focus-visible:outline-2 focus-visible:outline-tsb11-acc focus-visible:outline-offset-2 group-has-checked:justify-center group-has-checked:gap-0" href="#tsb-11-main"><span class="flex-none text-[0.6rem] text-tsb11-mut" aria-hidden="true">●</span><span class="min-w-0 overflow-hidden text-ellipsis group-has-checked:absolute group-has-checked:w-px group-has-checked:h-px group-has-checked:whitespace-nowrap group-has-checked:[clip-path:inset(50%)]">Security</span></a></li>
<li><a class="flex items-center gap-3 min-w-0 min-h-11 px-2.5 py-2 rounded-[1.4rem] text-[0.92rem] text-tsb11-ink no-underline whitespace-nowrap hover:bg-tsb11-surf focus-visible:outline-2 focus-visible:outline-tsb11-acc focus-visible:outline-offset-2 group-has-checked:justify-center group-has-checked:gap-0" href="#tsb-11-main"><span class="flex-none text-[0.6rem] text-tsb11-mut" aria-hidden="true">●</span><span class="min-w-0 overflow-hidden text-ellipsis group-has-checked:absolute group-has-checked:w-px group-has-checked:h-px group-has-checked:whitespace-nowrap group-has-checked:[clip-path:inset(50%)]">Billing</span></a></li>
<li><a class="flex items-center gap-3 min-w-0 min-h-11 px-2.5 py-2 rounded-[1.4rem] text-[0.92rem] text-tsb11-ink no-underline whitespace-nowrap hover:bg-tsb11-surf focus-visible:outline-2 focus-visible:outline-tsb11-acc focus-visible:outline-offset-2 group-has-checked:justify-center group-has-checked:gap-0" href="#tsb-11-main"><span class="flex-none text-[0.6rem] text-tsb11-mut" aria-hidden="true">●</span><span class="min-w-0 overflow-hidden text-ellipsis group-has-checked:absolute group-has-checked:w-px group-has-checked:h-px group-has-checked:whitespace-nowrap group-has-checked:[clip-path:inset(50%)]">API keys</span></a></li>
</ul>
</nav>
<p class="m-0 px-4 pt-2.5 pb-3.5 overflow-hidden whitespace-nowrap text-[0.78rem] text-tsb11-mut"><span class="min-w-0 overflow-hidden text-ellipsis group-has-checked:absolute group-has-checked:w-px group-has-checked:h-px group-has-checked:whitespace-nowrap group-has-checked:[clip-path:inset(50%)]">Signed in as Priya Sharma</span></p>
</aside>
<main class="flex-1 min-w-0 px-5 pt-6 pb-10 r46:px-9 r46:pt-10 r46:pb-12" id="tsb-11-main">
<h2 class="m-0 mb-5 font-tsb11-display text-[clamp(1.35rem,3.4vw,1.9rem)] leading-[1.15]">Profile</h2>
<dl class="m-0 grid grid-cols-[minmax(0,1fr)] gap-2.5 max-w-[44rem] r34:grid-cols-2 [&>div]:min-w-0 [&>div]:px-3.5 [&>div]:py-3 [&>div]:rounded-[0.9rem] [&>div]:bg-tsb11-surf2 [&_dt]:text-[0.72rem] [&_dt]:tracking-[0.08em] [&_dt]:uppercase [&_dt]:text-tsb11-mut [&_dd]:mt-0.5 [&_dd]:ml-0 [&_dd]:text-[0.92rem]">
<div><dt>Display name</dt><dd>Priya Sharma</dd></div>
<div><dt>Workspace</dt><dd>Vale · Operations</dd></div>
<div><dt>Time zone</dt><dd>Europe/Lisbon (UTC+1)</dd></div>
<div><dt>Two-factor</dt><dd>Authenticator app</dd></div>
<div><dt>Recovery email</dt><dd>p.sharma@vale.example</dd></div>
<div><dt>Member since</dt><dd>March 2024</dd></div>
</dl>
</main>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tsb11-bg: #f5f1ee;
--color-tsb11-bg: oklch(95.5% 0.008 45);
--color-tsb11-surf: #fffcfa;
--color-tsb11-surf: oklch(99% 0.004 45);
--color-tsb11-surf2: #ebe3dd;
--color-tsb11-surf2: oklch(92% 0.014 45);
--color-tsb11-line: #d9cdc4;
--color-tsb11-line: oklch(85% 0.02 45);
--color-tsb11-ink: #241c17;
--color-tsb11-ink: oklch(24% 0.018 45);
--color-tsb11-mut: #6d5c52;
--color-tsb11-mut: oklch(48% 0.025 45);
--color-tsb11-acc: #7d4a35;
--color-tsb11-acc: oklch(46% 0.09 40);
--color-tsb11-accsoft: #eee0d8;
--color-tsb11-accsoft: oklch(92% 0.02 45);
--color-tsb11-onacc: #fffcfa;
--color-tsb11-onacc: oklch(99% 0.004 45);
--font-tsb11-sans: system-ui, -apple-system, 'Segoe UI', sans-serif;
--font-tsb11-display: 'Quicksand', Nunito, ui-rounded, system-ui, sans-serif;
--breakpoint-r30: 30rem;
--breakpoint-r34: 34rem;
--breakpoint-r46: 46rem;
}
/* Root rule: the full-bleed contract plus the two rail widths. */
.tsb-11 {
width: 100%;
min-height: 100svh;
--tsb11-w-open: 15rem;
--tsb11-w-rail: 4.5rem;
}
.tsb-11 *,
.tsb-11 *::before,
.tsb-11 *::after {
box-sizing: border-box;
}
/* Token flip for the pure-CSS theme toggle, plus the two mandatory mirror blocks.
Note this is a SECOND checkbox in the demo, independent of the collapse one — which is
why the collapse group is scoped to the app shell, not to the section root. */
.tsb-11:has(.tsb-11-theme-input:checked) {
--color-tsb11-bg: #14100e;
--color-tsb11-surf: #1c1714;
--color-tsb11-surf2: #251e1a;
--color-tsb11-line: #362c26;
--color-tsb11-ink: #f1e9e3;
--color-tsb11-mut: #a89689;
--color-tsb11-acc: #dda183;
--color-tsb11-acc: oklch(78% 0.09 45);
--color-tsb11-accsoft: #32241d;
--color-tsb11-onacc: #1c1714;
}
@media (prefers-color-scheme: dark) {
.tsb-11 {
--color-tsb11-bg: #14100e;
--color-tsb11-surf: #1c1714;
--color-tsb11-surf2: #251e1a;
--color-tsb11-line: #362c26;
--color-tsb11-ink: #f1e9e3;
--color-tsb11-mut: #a89689;
--color-tsb11-acc: #dda183;
--color-tsb11-acc: oklch(78% 0.09 45);
--color-tsb11-accsoft: #32241d;
--color-tsb11-onacc: #1c1714;
}
.tsb-11:has(.tsb-11-theme-input:checked) {
--color-tsb11-bg: #f5f1ee;
--color-tsb11-surf: #fffcfa;
--color-tsb11-surf2: #ebe3dd;
--color-tsb11-line: #d9cdc4;
--color-tsb11-ink: #241c17;
--color-tsb11-mut: #6d5c52;
--color-tsb11-acc: #7d4a35;
--color-tsb11-acc: oklch(46% 0.09 40);
--color-tsb11-accsoft: #eee0d8;
--color-tsb11-onacc: #fffcfa;
}
}
@media (prefers-color-scheme: light) {
.tsb-11:has(.tsb-11-theme-input:checked) {
--color-tsb11-bg: #14100e;
--color-tsb11-surf: #1c1714;
--color-tsb11-surf2: #251e1a;
--color-tsb11-line: #362c26;
--color-tsb11-ink: #f1e9e3;
--color-tsb11-mut: #a89689;
--color-tsb11-acc: #dda183;
--color-tsb11-acc: oklch(78% 0.09 45);
--color-tsb11-accsoft: #32241d;
--color-tsb11-onacc: #1c1714;
}
}
```How this works
The checkbox is the state, the label is the button. The input is visually hidden but focusable; the <label for> is a 44px target that toggles it, so keyboard and pointer both work with no script. Everything else is a variant:
<div class="group flex">
<input type="checkbox" id="tsb-11-collapse" class="absolute w-px h-px …">
<aside class="w-[var(--tsb11-w-open)]
group-has-checked:w-[var(--tsb11-w-rail)]">group-has-checked: is the whole mechanism, and it is worth naming why it exists. Before :has(), a checkbox could only style LATER SIBLINGS, which forced the input to sit in front of everything it affected and made the markup order a puppet of the CSS. With :has() on a wrapper the input can go anywhere inside the group.
What this pattern cannot do. A checkbox has checked, not aria-expanded, and a <label> is not a button. Screen readers announce “Collapse, checkbox” rather than “Collapse sidebar, button, expanded”. For a marketing site that is acceptable; for an application sidebar, demo 05's scripted version is the correct build.
Labels stay announced. Collapsing them uses the visually hidden utilities, not hidden — same rule as demo 05, and the same reason.
Make it yours
- Ship collapsed by default with the
checkedattribute on the input. - Rail widths:
--tsb11-w-openand--tsb11-w-railin the root rule. - Swap the caption pair for an icon rotation if you prefer; the state variant is indifferent.
- Retheme through
--color-tsb11-accand--color-tsb11-accsoft; the active pill and the focus rings read them. - Add tooltips to the collapsed icons with demo 13's hover/focus label.
- Need
aria-expanded, persistence, or a state your router knows about? Move to demo 05 or 07 — the visual half of the class list ports over unchanged.
Gotchas — read before shipping
- A
<label>is not a button and a checkbox is not a disclosure. The announcement ischeckbox, checked, which is the documented limit of this pattern. - The input must stay focusable — visually hidden, never
hiddenordisplay:none, or keyboard users lose the control entirely. group-has-checked:needs thegroupclass on a shared ancestor. On the rail alone it cannot see an input that lives outside the rail.peer-checked:is NOT a substitute here: it only reaches later siblings, so it would force the input to precede every element that responds.- Focus styling has to target the label through
group-has-[input:focus-visible]:, because the focus is on the hidden input, not on the visible control. - Collapsing labels with
hiddenremoves every accessible name in the rail. Use the visually hidden set.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
:has(), which group-has-* compiles to, is Baseline 2023 (Firefox 121). Tailwind v4's own floor is higher, so any engine that can run the generated sheet supports the mechanism; below it, the rail stays expanded and everything else works.