03 / 20
All 20 in 20 Tailwind CSS Sidebars
- 01Tailwind Accessible Sidebar Navigation ARIA Reference
- 02Tailwind Sidebar Focus Trap and Restore on Close
- 03Tailwind Inert Background Content Behind a Drawer
- 04Tailwind Sidebar Navigation Is Not a Tab List
- 05Tailwind Announcing Collapsed and Expanded Sidebar State
- 06Tailwind Roving Tabindex for Sidebar Keyboard Navigation
- 07Tailwind Persisting Sidebar Collapse State Across Reloads
- 08Tailwind Sidebar Scroll Containment with Overscroll Behavior
- 09Tailwind Nested Submenu Keyboard and Screen Reader Behavior
- 10Tailwind Resizable Sidebar with a Keyboard Accessible Handle
- 11Pure Tailwind Collapsible Sidebar with Checkbox State
- 12Tailwind Responsive Sidebar Breakpoint Behavior
- 13Pure Tailwind Icon Rail Sidebar with Accessible Labels
- 14Tailwind Dashboard Sidebar with Current Page State
- 15Pure Tailwind Multi Level Sidebar Menu Disclosure Pattern
- 16Pure Tailwind Accordion Sidebar with Details and Summary
- 17Pure Tailwind Sticky Full Height Sidebar Layout Mechanics
- 18Pure Tailwind Floating Sidebar Stacking and Overlay Order
- 19Tailwind Off Canvas Sidebar Drawer with Focus Return
- 20Pure Tailwind Sidebar Footer Pinning with Scrollable Nav
Tailwind Inert Background Content Behind a Drawer
The half of modal behaviour a focus trap cannot give you: everything behind the drawer is switched off with one attribute. inert takes the background out of the accessibility tree, out of the tab order and out of pointer events at once — and because Tailwind v4 ships an inert: variant, the visual consequence (a dimmed table) is declared on the element that goes inert, next to its normal state.
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-03 block relative w-full min-h-svh bg-tsb03-bg text-tsb03-ink font-tsb03-sans leading-[1.55]" aria-label="Sable order desk">
<div class="absolute top-3.5 right-3.5 z-60">
<input class="tsb-03-theme-input peer absolute inset-0 w-full h-full m-0 opacity-0 cursor-pointer" type="checkbox" id="tsb-03-theme">
<label class="grid place-items-center w-11 h-11 rounded-full border border-tsb03-line bg-tsb03-surf text-tsb03-ink cursor-pointer peer-focus-visible:outline-2 peer-focus-visible:outline-tsb03-acc peer-focus-visible:outline-offset-2" for="tsb-03-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 relative overflow-hidden min-h-svh bg-tsb03-surf">
<div class="min-w-0 transition-opacity duration-200 inert:opacity-45 motion-reduce:transition-none" id="tsb-03-content">
<div class="flex flex-wrap items-center gap-1.5 py-2.5 pl-4 pr-[4.2rem] bg-tsb03-surf2 border-b border-tsb03-line">
<p class="mr-auto m-0 font-tsb03-display text-[1.05rem]">Sable — order desk</p>
<a class="inline-flex items-center min-h-11 px-2.5 text-[0.88rem] font-semibold text-tsb03-acc no-underline hover:underline focus-visible:outline-2 focus-visible:outline-tsb03-acc focus-visible:outline-offset-2" href="#tsb-03-content">Fulfilment</a>
<a class="inline-flex items-center min-h-11 px-2.5 text-[0.88rem] font-semibold text-tsb03-acc no-underline hover:underline focus-visible:outline-2 focus-visible:outline-tsb03-acc focus-visible:outline-offset-2" href="#tsb-03-content">Returns</a>
<button class="min-h-11 px-4 border border-tsb03-acc bg-tsb03-acc text-tsb03-surf font-tsb03-sans text-[0.9rem] font-semibold cursor-pointer focus-visible:outline-2 focus-visible:outline-tsb03-ink focus-visible:outline-offset-2" type="button" id="tsb-03-open" aria-expanded="false" aria-controls="tsb-03-drawer">Filters</button>
</div>
<table class="w-full border-collapse text-[0.9rem]">
<thead><tr class="[&>th]:px-4 [&>th]:py-3 [&>th]:text-start [&>th]:border-b [&>th]:border-tsb03-line [&>th]:text-[0.74rem] [&>th]:tracking-[0.06em] [&>th]:uppercase [&>th]:font-semibold [&>th]:text-tsb03-mut"><th scope="col">Order</th><th scope="col">Customer</th><th scope="col">State</th><th scope="col"></th></tr></thead>
<tbody class="[&>tr>td]:px-4 [&>tr>td]:py-3 [&>tr>td]:border-b [&>tr>td]:border-tsb03-line [&>tr>td]:min-w-0">
<tr><td>SB-4417</td><td>Priya Sharma</td><td>Packing</td><td><a class="inline-flex items-center min-h-11 px-2.5 text-[0.88rem] font-semibold text-tsb03-acc no-underline hover:underline focus-visible:outline-2 focus-visible:outline-tsb03-acc focus-visible:outline-offset-2" href="#tsb-03-content">Open</a></td></tr>
<tr><td>SB-4418</td><td>Jordan Lee</td><td>Awaiting stock</td><td><a class="inline-flex items-center min-h-11 px-2.5 text-[0.88rem] font-semibold text-tsb03-acc no-underline hover:underline focus-visible:outline-2 focus-visible:outline-tsb03-acc focus-visible:outline-offset-2" href="#tsb-03-content">Open</a></td></tr>
<tr><td>SB-4421</td><td>Sam Rivera</td><td>Ready to ship</td><td><a class="inline-flex items-center min-h-11 px-2.5 text-[0.88rem] font-semibold text-tsb03-acc no-underline hover:underline focus-visible:outline-2 focus-visible:outline-tsb03-acc focus-visible:outline-offset-2" href="#tsb-03-content">Open</a></td></tr>
<tr><td>SB-4423</td><td>Ada Okafor</td><td>Packing</td><td><a class="inline-flex items-center min-h-11 px-2.5 text-[0.88rem] font-semibold text-tsb03-acc no-underline hover:underline focus-visible:outline-2 focus-visible:outline-tsb03-acc focus-visible:outline-offset-2" href="#tsb-03-content">Open</a></td></tr>
<tr><td>SB-4425</td><td>Tomas Vidal</td><td>Awaiting stock</td><td><a class="inline-flex items-center min-h-11 px-2.5 text-[0.88rem] font-semibold text-tsb03-acc no-underline hover:underline focus-visible:outline-2 focus-visible:outline-tsb03-acc focus-visible:outline-offset-2" href="#tsb-03-content">Open</a></td></tr>
</tbody>
</table>
</div>
<div class="absolute inset-0 z-20 bg-[oklch(25%_0.02_60/0.42)] [&[hidden]]:hidden" data-tsb-03-scrim hidden></div>
<aside class="absolute inset-y-0 right-0 z-30 flex flex-col w-[min(18rem,84%)] bg-tsb03-surf border-l border-tsb03-line translate-x-full transition-transform duration-[220ms] ease-out group-data-open:translate-x-0 motion-reduce:duration-[1ms] [&[hidden]]:hidden" id="tsb-03-drawer" role="dialog" aria-modal="true" aria-labelledby="tsb-03-dh" hidden>
<header class="flex items-center gap-2 px-3 py-2.5 border-b border-tsb03-line">
<h3 class="mr-auto m-0 font-tsb03-display text-[1.1rem]" id="tsb-03-dh">Filters</h3>
<button class="grid place-items-center w-11 h-11 border border-tsb03-line bg-transparent text-tsb03-ink cursor-pointer [&>svg]:w-4 [&>svg]:h-4 focus-visible:outline-2 focus-visible:outline-tsb03-acc focus-visible:-outline-offset-2" type="button" data-tsb-03-close>
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M6 6l12 12M18 6L6 18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>
<span class="absolute w-px h-px overflow-hidden whitespace-nowrap [clip-path:inset(50%)]">Close filters</span>
</button>
</header>
<nav class="flex-1 min-h-0 overflow-y-auto [overscroll-behavior:contain]" aria-label="Order views">
<ul class="list-none m-0 p-0">
<li><a class="flex items-center min-w-0 min-h-11 px-3 py-2 text-[0.9rem] text-tsb03-ink no-underline border-b border-tsb03-line hover:bg-tsb03-surf2 focus-visible:outline-2 focus-visible:outline-tsb03-acc focus-visible:-outline-offset-2 aria-[current=page]:bg-tsb03-accsoft aria-[current=page]:text-tsb03-acc aria-[current=page]:font-semibold" href="#tsb-03-content" aria-current="page">All open orders</a></li>
<li><a class="flex items-center min-w-0 min-h-11 px-3 py-2 text-[0.9rem] text-tsb03-ink no-underline border-b border-tsb03-line hover:bg-tsb03-surf2 focus-visible:outline-2 focus-visible:outline-tsb03-acc focus-visible:-outline-offset-2" href="#tsb-03-content">Awaiting stock</a></li>
<li><a class="flex items-center min-w-0 min-h-11 px-3 py-2 text-[0.9rem] text-tsb03-ink no-underline border-b border-tsb03-line hover:bg-tsb03-surf2 focus-visible:outline-2 focus-visible:outline-tsb03-acc focus-visible:-outline-offset-2" href="#tsb-03-content">Ready to ship</a></li>
<li><a class="flex items-center min-w-0 min-h-11 px-3 py-2 text-[0.9rem] text-tsb03-ink no-underline border-b border-tsb03-line hover:bg-tsb03-surf2 focus-visible:outline-2 focus-visible:outline-tsb03-acc focus-visible:-outline-offset-2" href="#tsb-03-content">Returns queue</a></li>
<li><a class="flex items-center min-w-0 min-h-11 px-3 py-2 text-[0.9rem] text-tsb03-ink no-underline border-b border-tsb03-line hover:bg-tsb03-surf2 focus-visible:outline-2 focus-visible:outline-tsb03-acc focus-visible:-outline-offset-2" href="#tsb-03-content">Archived</a></li>
</ul>
</nav>
</aside>
</div>
</section><section class="tsb-03 block relative w-full min-h-svh bg-tsb03-bg text-tsb03-ink font-tsb03-sans leading-[1.55]" aria-label="Sable order desk">
<div class="absolute top-3.5 right-3.5 z-60">
<input class="tsb-03-theme-input peer absolute inset-0 w-full h-full m-0 opacity-0 cursor-pointer" type="checkbox" id="tsb-03-theme">
<label class="grid place-items-center w-11 h-11 rounded-full border border-tsb03-line bg-tsb03-surf text-tsb03-ink cursor-pointer peer-focus-visible:outline-2 peer-focus-visible:outline-tsb03-acc peer-focus-visible:outline-offset-2" for="tsb-03-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 relative overflow-hidden min-h-svh bg-tsb03-surf">
<div class="min-w-0 transition-opacity duration-200 inert:opacity-45 motion-reduce:transition-none" id="tsb-03-content">
<div class="flex flex-wrap items-center gap-1.5 py-2.5 pl-4 pr-[4.2rem] bg-tsb03-surf2 border-b border-tsb03-line">
<p class="mr-auto m-0 font-tsb03-display text-[1.05rem]">Sable — order desk</p>
<a class="inline-flex items-center min-h-11 px-2.5 text-[0.88rem] font-semibold text-tsb03-acc no-underline hover:underline focus-visible:outline-2 focus-visible:outline-tsb03-acc focus-visible:outline-offset-2" href="#tsb-03-content">Fulfilment</a>
<a class="inline-flex items-center min-h-11 px-2.5 text-[0.88rem] font-semibold text-tsb03-acc no-underline hover:underline focus-visible:outline-2 focus-visible:outline-tsb03-acc focus-visible:outline-offset-2" href="#tsb-03-content">Returns</a>
<button class="min-h-11 px-4 border border-tsb03-acc bg-tsb03-acc text-tsb03-surf font-tsb03-sans text-[0.9rem] font-semibold cursor-pointer focus-visible:outline-2 focus-visible:outline-tsb03-ink focus-visible:outline-offset-2" type="button" id="tsb-03-open" aria-expanded="false" aria-controls="tsb-03-drawer">Filters</button>
</div>
<table class="w-full border-collapse text-[0.9rem]">
<thead><tr class="[&>th]:px-4 [&>th]:py-3 [&>th]:text-start [&>th]:border-b [&>th]:border-tsb03-line [&>th]:text-[0.74rem] [&>th]:tracking-[0.06em] [&>th]:uppercase [&>th]:font-semibold [&>th]:text-tsb03-mut"><th scope="col">Order</th><th scope="col">Customer</th><th scope="col">State</th><th scope="col"></th></tr></thead>
<tbody class="[&>tr>td]:px-4 [&>tr>td]:py-3 [&>tr>td]:border-b [&>tr>td]:border-tsb03-line [&>tr>td]:min-w-0">
<tr><td>SB-4417</td><td>Priya Sharma</td><td>Packing</td><td><a class="inline-flex items-center min-h-11 px-2.5 text-[0.88rem] font-semibold text-tsb03-acc no-underline hover:underline focus-visible:outline-2 focus-visible:outline-tsb03-acc focus-visible:outline-offset-2" href="#tsb-03-content">Open</a></td></tr>
<tr><td>SB-4418</td><td>Jordan Lee</td><td>Awaiting stock</td><td><a class="inline-flex items-center min-h-11 px-2.5 text-[0.88rem] font-semibold text-tsb03-acc no-underline hover:underline focus-visible:outline-2 focus-visible:outline-tsb03-acc focus-visible:outline-offset-2" href="#tsb-03-content">Open</a></td></tr>
<tr><td>SB-4421</td><td>Sam Rivera</td><td>Ready to ship</td><td><a class="inline-flex items-center min-h-11 px-2.5 text-[0.88rem] font-semibold text-tsb03-acc no-underline hover:underline focus-visible:outline-2 focus-visible:outline-tsb03-acc focus-visible:outline-offset-2" href="#tsb-03-content">Open</a></td></tr>
<tr><td>SB-4423</td><td>Ada Okafor</td><td>Packing</td><td><a class="inline-flex items-center min-h-11 px-2.5 text-[0.88rem] font-semibold text-tsb03-acc no-underline hover:underline focus-visible:outline-2 focus-visible:outline-tsb03-acc focus-visible:outline-offset-2" href="#tsb-03-content">Open</a></td></tr>
<tr><td>SB-4425</td><td>Tomas Vidal</td><td>Awaiting stock</td><td><a class="inline-flex items-center min-h-11 px-2.5 text-[0.88rem] font-semibold text-tsb03-acc no-underline hover:underline focus-visible:outline-2 focus-visible:outline-tsb03-acc focus-visible:outline-offset-2" href="#tsb-03-content">Open</a></td></tr>
</tbody>
</table>
</div>
<div class="absolute inset-0 z-20 bg-[oklch(25%_0.02_60/0.42)] [&[hidden]]:hidden" data-tsb-03-scrim hidden></div>
<aside class="absolute inset-y-0 right-0 z-30 flex flex-col w-[min(18rem,84%)] bg-tsb03-surf border-l border-tsb03-line translate-x-full transition-transform duration-[220ms] ease-out group-data-open:translate-x-0 motion-reduce:duration-[1ms] [&[hidden]]:hidden" id="tsb-03-drawer" role="dialog" aria-modal="true" aria-labelledby="tsb-03-dh" hidden>
<header class="flex items-center gap-2 px-3 py-2.5 border-b border-tsb03-line">
<h3 class="mr-auto m-0 font-tsb03-display text-[1.1rem]" id="tsb-03-dh">Filters</h3>
<button class="grid place-items-center w-11 h-11 border border-tsb03-line bg-transparent text-tsb03-ink cursor-pointer [&>svg]:w-4 [&>svg]:h-4 focus-visible:outline-2 focus-visible:outline-tsb03-acc focus-visible:-outline-offset-2" type="button" data-tsb-03-close>
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M6 6l12 12M18 6L6 18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>
<span class="absolute w-px h-px overflow-hidden whitespace-nowrap [clip-path:inset(50%)]">Close filters</span>
</button>
</header>
<nav class="flex-1 min-h-0 overflow-y-auto [overscroll-behavior:contain]" aria-label="Order views">
<ul class="list-none m-0 p-0">
<li><a class="flex items-center min-w-0 min-h-11 px-3 py-2 text-[0.9rem] text-tsb03-ink no-underline border-b border-tsb03-line hover:bg-tsb03-surf2 focus-visible:outline-2 focus-visible:outline-tsb03-acc focus-visible:-outline-offset-2 aria-[current=page]:bg-tsb03-accsoft aria-[current=page]:text-tsb03-acc aria-[current=page]:font-semibold" href="#tsb-03-content" aria-current="page">All open orders</a></li>
<li><a class="flex items-center min-w-0 min-h-11 px-3 py-2 text-[0.9rem] text-tsb03-ink no-underline border-b border-tsb03-line hover:bg-tsb03-surf2 focus-visible:outline-2 focus-visible:outline-tsb03-acc focus-visible:-outline-offset-2" href="#tsb-03-content">Awaiting stock</a></li>
<li><a class="flex items-center min-w-0 min-h-11 px-3 py-2 text-[0.9rem] text-tsb03-ink no-underline border-b border-tsb03-line hover:bg-tsb03-surf2 focus-visible:outline-2 focus-visible:outline-tsb03-acc focus-visible:-outline-offset-2" href="#tsb-03-content">Ready to ship</a></li>
<li><a class="flex items-center min-w-0 min-h-11 px-3 py-2 text-[0.9rem] text-tsb03-ink no-underline border-b border-tsb03-line hover:bg-tsb03-surf2 focus-visible:outline-2 focus-visible:outline-tsb03-acc focus-visible:-outline-offset-2" href="#tsb-03-content">Returns queue</a></li>
<li><a class="flex items-center min-w-0 min-h-11 px-3 py-2 text-[0.9rem] text-tsb03-ink no-underline border-b border-tsb03-line hover:bg-tsb03-surf2 focus-visible:outline-2 focus-visible:outline-tsb03-acc focus-visible:-outline-offset-2" href="#tsb-03-content">Archived</a></li>
</ul>
</nav>
</aside>
</div>
</section>@import "tailwindcss";
@theme {
--color-tsb03-bg: #f6f1e8;
--color-tsb03-bg: oklch(95% 0.018 85);
--color-tsb03-surf: #fffdf9;
--color-tsb03-surf: oklch(99% 0.008 85);
--color-tsb03-surf2: #efe7da;
--color-tsb03-surf2: oklch(92% 0.022 80);
--color-tsb03-line: #ddd1bd;
--color-tsb03-line: oklch(85% 0.032 80);
--color-tsb03-ink: #23201a;
--color-tsb03-ink: oklch(24% 0.012 70);
--color-tsb03-mut: #6d6455;
--color-tsb03-mut: oklch(48% 0.02 75);
--color-tsb03-acc: #8a4b1f;
--color-tsb03-acc: oklch(48% 0.12 52);
--color-tsb03-accsoft: #f2e4d5;
--color-tsb03-accsoft: oklch(92% 0.03 60);
--font-tsb03-sans: system-ui, -apple-system, 'Segoe UI', sans-serif;
--font-tsb03-display: 'Source Serif 4', 'Iowan Old Style', Palatino, Georgia, serif;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's
w-full min-h-svh. It scopes the demo, and it holds the stage at full height in the
moment before the browser build has compiled its utilities. */
.tsb-03 {
width: 100%;
min-height: 100svh;
}
.tsb-03 *,
.tsb-03 *::before,
.tsb-03 *::after {
box-sizing: border-box;
}
/* Token flip for the pure-CSS theme toggle, plus the two mandatory mirror blocks. */
.tsb-03:has(.tsb-03-theme-input:checked) {
--color-tsb03-bg: #17140f;
--color-tsb03-surf: #1f1b15;
--color-tsb03-surf2: #292319;
--color-tsb03-line: #3a3226;
--color-tsb03-ink: #f2ece1;
--color-tsb03-mut: #a89b87;
--color-tsb03-acc: #e6a06a;
--color-tsb03-acc: oklch(78% 0.11 55);
--color-tsb03-accsoft: #35291d;
}
@media (prefers-color-scheme: dark) {
.tsb-03 {
--color-tsb03-bg: #17140f;
--color-tsb03-surf: #1f1b15;
--color-tsb03-surf2: #292319;
--color-tsb03-line: #3a3226;
--color-tsb03-ink: #f2ece1;
--color-tsb03-mut: #a89b87;
--color-tsb03-acc: #e6a06a;
--color-tsb03-acc: oklch(78% 0.11 55);
--color-tsb03-accsoft: #35291d;
}
.tsb-03:has(.tsb-03-theme-input:checked) {
--color-tsb03-bg: #f6f1e8;
--color-tsb03-surf: #fffdf9;
--color-tsb03-surf2: #efe7da;
--color-tsb03-line: #ddd1bd;
--color-tsb03-ink: #23201a;
--color-tsb03-mut: #6d6455;
--color-tsb03-acc: #8a4b1f;
--color-tsb03-acc: oklch(48% 0.12 52);
--color-tsb03-accsoft: #f2e4d5;
}
}
@media (prefers-color-scheme: light) {
.tsb-03:has(.tsb-03-theme-input:checked) {
--color-tsb03-bg: #17140f;
--color-tsb03-surf: #1f1b15;
--color-tsb03-surf2: #292319;
--color-tsb03-line: #3a3226;
--color-tsb03-ink: #f2ece1;
--color-tsb03-mut: #a89b87;
--color-tsb03-acc: #e6a06a;
--color-tsb03-acc: oklch(78% 0.11 55);
--color-tsb03-accsoft: #35291d;
}
}@import "tailwindcss";
@theme {
--color-tsb03-bg: #f6f1e8;
--color-tsb03-bg: oklch(95% 0.018 85);
--color-tsb03-surf: #fffdf9;
--color-tsb03-surf: oklch(99% 0.008 85);
--color-tsb03-surf2: #efe7da;
--color-tsb03-surf2: oklch(92% 0.022 80);
--color-tsb03-line: #ddd1bd;
--color-tsb03-line: oklch(85% 0.032 80);
--color-tsb03-ink: #23201a;
--color-tsb03-ink: oklch(24% 0.012 70);
--color-tsb03-mut: #6d6455;
--color-tsb03-mut: oklch(48% 0.02 75);
--color-tsb03-acc: #8a4b1f;
--color-tsb03-acc: oklch(48% 0.12 52);
--color-tsb03-accsoft: #f2e4d5;
--color-tsb03-accsoft: oklch(92% 0.03 60);
--font-tsb03-sans: system-ui, -apple-system, 'Segoe UI', sans-serif;
--font-tsb03-display: 'Source Serif 4', 'Iowan Old Style', Palatino, Georgia, serif;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's
w-full min-h-svh. It scopes the demo, and it holds the stage at full height in the
moment before the browser build has compiled its utilities. */
.tsb-03 {
width: 100%;
min-height: 100svh;
}
.tsb-03 *,
.tsb-03 *::before,
.tsb-03 *::after {
box-sizing: border-box;
}
/* Token flip for the pure-CSS theme toggle, plus the two mandatory mirror blocks. */
.tsb-03:has(.tsb-03-theme-input:checked) {
--color-tsb03-bg: #17140f;
--color-tsb03-surf: #1f1b15;
--color-tsb03-surf2: #292319;
--color-tsb03-line: #3a3226;
--color-tsb03-ink: #f2ece1;
--color-tsb03-mut: #a89b87;
--color-tsb03-acc: #e6a06a;
--color-tsb03-acc: oklch(78% 0.11 55);
--color-tsb03-accsoft: #35291d;
}
@media (prefers-color-scheme: dark) {
.tsb-03 {
--color-tsb03-bg: #17140f;
--color-tsb03-surf: #1f1b15;
--color-tsb03-surf2: #292319;
--color-tsb03-line: #3a3226;
--color-tsb03-ink: #f2ece1;
--color-tsb03-mut: #a89b87;
--color-tsb03-acc: #e6a06a;
--color-tsb03-acc: oklch(78% 0.11 55);
--color-tsb03-accsoft: #35291d;
}
.tsb-03:has(.tsb-03-theme-input:checked) {
--color-tsb03-bg: #f6f1e8;
--color-tsb03-surf: #fffdf9;
--color-tsb03-surf2: #efe7da;
--color-tsb03-line: #ddd1bd;
--color-tsb03-ink: #23201a;
--color-tsb03-mut: #6d6455;
--color-tsb03-acc: #8a4b1f;
--color-tsb03-acc: oklch(48% 0.12 52);
--color-tsb03-accsoft: #f2e4d5;
}
}
@media (prefers-color-scheme: light) {
.tsb-03:has(.tsb-03-theme-input:checked) {
--color-tsb03-bg: #17140f;
--color-tsb03-surf: #1f1b15;
--color-tsb03-surf2: #292319;
--color-tsb03-line: #3a3226;
--color-tsb03-ink: #f2ece1;
--color-tsb03-mut: #a89b87;
--color-tsb03-acc: #e6a06a;
--color-tsb03-acc: oklch(78% 0.11 55);
--color-tsb03-accsoft: #35291d;
}
}(function () {
document.querySelectorAll('.tsb-03').forEach(function (root) {
if (root.dataset.bound) return; root.dataset.bound = '1';
var stage = root.querySelector('.group'),
drawer = root.querySelector('#tsb-03-drawer'),
scrim = root.querySelector('[data-tsb-03-scrim]'),
content = root.querySelector('#tsb-03-content'),
openBtn = root.querySelector('#tsb-03-open'),
supported = 'inert' in HTMLElement.prototype,
returnTo = null;
function behind() { return Array.prototype.slice.call(content.querySelectorAll('a[href], button:not([disabled])')); }
function block(on) {
if (supported) { content.inert = on; return; }
content.setAttribute('aria-hidden', String(on));
behind().forEach(function (el) { on ? el.setAttribute('tabindex', '-1') : el.removeAttribute('tabindex'); });
}
function onKey(e) { if (e.key === 'Escape') setOpen(false); }
function setOpen(next) {
if (next) returnTo = document.activeElement;
drawer.hidden = scrim.hidden = !next;
stage.toggleAttribute('data-open', next);
openBtn.setAttribute('aria-expanded', String(next));
block(next);
if (next) {
document.addEventListener('keydown', onKey);
requestAnimationFrame(function () { drawer.querySelector('[data-tsb-03-close]').focus(); });
} else {
document.removeEventListener('keydown', onKey);
(returnTo || openBtn).focus({ preventScroll: true });
}
}
openBtn.addEventListener('click', function () { setOpen(true); });
scrim.addEventListener('click', function () { setOpen(false); });
root.querySelectorAll('[data-tsb-03-close]').forEach(function (b) { b.addEventListener('click', function () { setOpen(false); }); });
root.__tsbCleanup = function () { document.removeEventListener('keydown', onKey); };
});
})();(function () {
document.querySelectorAll('.tsb-03').forEach(function (root) {
if (root.dataset.bound) return; root.dataset.bound = '1';
var stage = root.querySelector('.group'),
drawer = root.querySelector('#tsb-03-drawer'),
scrim = root.querySelector('[data-tsb-03-scrim]'),
content = root.querySelector('#tsb-03-content'),
openBtn = root.querySelector('#tsb-03-open'),
supported = 'inert' in HTMLElement.prototype,
returnTo = null;
function behind() { return Array.prototype.slice.call(content.querySelectorAll('a[href], button:not([disabled])')); }
function block(on) {
if (supported) { content.inert = on; return; }
content.setAttribute('aria-hidden', String(on));
behind().forEach(function (el) { on ? el.setAttribute('tabindex', '-1') : el.removeAttribute('tabindex'); });
}
function onKey(e) { if (e.key === 'Escape') setOpen(false); }
function setOpen(next) {
if (next) returnTo = document.activeElement;
drawer.hidden = scrim.hidden = !next;
stage.toggleAttribute('data-open', next);
openBtn.setAttribute('aria-expanded', String(next));
block(next);
if (next) {
document.addEventListener('keydown', onKey);
requestAnimationFrame(function () { drawer.querySelector('[data-tsb-03-close]').focus(); });
} else {
document.removeEventListener('keydown', onKey);
(returnTo || openBtn).focus({ preventScroll: true });
}
}
openBtn.addEventListener('click', function () { setOpen(true); });
scrim.addEventListener('click', function () { setOpen(false); });
root.querySelectorAll('[data-tsb-03-close]').forEach(function (b) { b.addEventListener('click', function () { setOpen(false); }); });
root.__tsbCleanup = function () { document.removeEventListener('keydown', onKey); };
});
})();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-tsb03-bg: #f6f1e8;
--color-tsb03-bg: oklch(95% 0.018 85);
--color-tsb03-surf: #fffdf9;
--color-tsb03-surf: oklch(99% 0.008 85);
--color-tsb03-surf2: #efe7da;
--color-tsb03-surf2: oklch(92% 0.022 80);
--color-tsb03-line: #ddd1bd;
--color-tsb03-line: oklch(85% 0.032 80);
--color-tsb03-ink: #23201a;
--color-tsb03-ink: oklch(24% 0.012 70);
--color-tsb03-mut: #6d6455;
--color-tsb03-mut: oklch(48% 0.02 75);
--color-tsb03-acc: #8a4b1f;
--color-tsb03-acc: oklch(48% 0.12 52);
--color-tsb03-accsoft: #f2e4d5;
--color-tsb03-accsoft: oklch(92% 0.03 60);
--font-tsb03-sans: system-ui, -apple-system, 'Segoe UI', sans-serif;
--font-tsb03-display: 'Source Serif 4', 'Iowan Old Style', Palatino, Georgia, serif;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's
w-full min-h-svh. It scopes the demo, and it holds the stage at full height in the
moment before the browser build has compiled its utilities. */
.tsb-03 {
width: 100%;
min-height: 100svh;
}
.tsb-03 *,
.tsb-03 *::before,
.tsb-03 *::after {
box-sizing: border-box;
}
/* Token flip for the pure-CSS theme toggle, plus the two mandatory mirror blocks. */
.tsb-03:has(.tsb-03-theme-input:checked) {
--color-tsb03-bg: #17140f;
--color-tsb03-surf: #1f1b15;
--color-tsb03-surf2: #292319;
--color-tsb03-line: #3a3226;
--color-tsb03-ink: #f2ece1;
--color-tsb03-mut: #a89b87;
--color-tsb03-acc: #e6a06a;
--color-tsb03-acc: oklch(78% 0.11 55);
--color-tsb03-accsoft: #35291d;
}
@media (prefers-color-scheme: dark) {
.tsb-03 {
--color-tsb03-bg: #17140f;
--color-tsb03-surf: #1f1b15;
--color-tsb03-surf2: #292319;
--color-tsb03-line: #3a3226;
--color-tsb03-ink: #f2ece1;
--color-tsb03-mut: #a89b87;
--color-tsb03-acc: #e6a06a;
--color-tsb03-acc: oklch(78% 0.11 55);
--color-tsb03-accsoft: #35291d;
}
.tsb-03:has(.tsb-03-theme-input:checked) {
--color-tsb03-bg: #f6f1e8;
--color-tsb03-surf: #fffdf9;
--color-tsb03-surf2: #efe7da;
--color-tsb03-line: #ddd1bd;
--color-tsb03-ink: #23201a;
--color-tsb03-mut: #6d6455;
--color-tsb03-acc: #8a4b1f;
--color-tsb03-acc: oklch(48% 0.12 52);
--color-tsb03-accsoft: #f2e4d5;
}
}
@media (prefers-color-scheme: light) {
.tsb-03:has(.tsb-03-theme-input:checked) {
--color-tsb03-bg: #17140f;
--color-tsb03-surf: #1f1b15;
--color-tsb03-surf2: #292319;
--color-tsb03-line: #3a3226;
--color-tsb03-ink: #f2ece1;
--color-tsb03-mut: #a89b87;
--color-tsb03-acc: #e6a06a;
--color-tsb03-acc: oklch(78% 0.11 55);
--color-tsb03-accsoft: #35291d;
}
}@import "tailwindcss";
@theme {
--color-tsb03-bg: #f6f1e8;
--color-tsb03-bg: oklch(95% 0.018 85);
--color-tsb03-surf: #fffdf9;
--color-tsb03-surf: oklch(99% 0.008 85);
--color-tsb03-surf2: #efe7da;
--color-tsb03-surf2: oklch(92% 0.022 80);
--color-tsb03-line: #ddd1bd;
--color-tsb03-line: oklch(85% 0.032 80);
--color-tsb03-ink: #23201a;
--color-tsb03-ink: oklch(24% 0.012 70);
--color-tsb03-mut: #6d6455;
--color-tsb03-mut: oklch(48% 0.02 75);
--color-tsb03-acc: #8a4b1f;
--color-tsb03-acc: oklch(48% 0.12 52);
--color-tsb03-accsoft: #f2e4d5;
--color-tsb03-accsoft: oklch(92% 0.03 60);
--font-tsb03-sans: system-ui, -apple-system, 'Segoe UI', sans-serif;
--font-tsb03-display: 'Source Serif 4', 'Iowan Old Style', Palatino, Georgia, serif;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's
w-full min-h-svh. It scopes the demo, and it holds the stage at full height in the
moment before the browser build has compiled its utilities. */
.tsb-03 {
width: 100%;
min-height: 100svh;
}
.tsb-03 *,
.tsb-03 *::before,
.tsb-03 *::after {
box-sizing: border-box;
}
/* Token flip for the pure-CSS theme toggle, plus the two mandatory mirror blocks. */
.tsb-03:has(.tsb-03-theme-input:checked) {
--color-tsb03-bg: #17140f;
--color-tsb03-surf: #1f1b15;
--color-tsb03-surf2: #292319;
--color-tsb03-line: #3a3226;
--color-tsb03-ink: #f2ece1;
--color-tsb03-mut: #a89b87;
--color-tsb03-acc: #e6a06a;
--color-tsb03-acc: oklch(78% 0.11 55);
--color-tsb03-accsoft: #35291d;
}
@media (prefers-color-scheme: dark) {
.tsb-03 {
--color-tsb03-bg: #17140f;
--color-tsb03-surf: #1f1b15;
--color-tsb03-surf2: #292319;
--color-tsb03-line: #3a3226;
--color-tsb03-ink: #f2ece1;
--color-tsb03-mut: #a89b87;
--color-tsb03-acc: #e6a06a;
--color-tsb03-acc: oklch(78% 0.11 55);
--color-tsb03-accsoft: #35291d;
}
.tsb-03:has(.tsb-03-theme-input:checked) {
--color-tsb03-bg: #f6f1e8;
--color-tsb03-surf: #fffdf9;
--color-tsb03-surf2: #efe7da;
--color-tsb03-line: #ddd1bd;
--color-tsb03-ink: #23201a;
--color-tsb03-mut: #6d6455;
--color-tsb03-acc: #8a4b1f;
--color-tsb03-acc: oklch(48% 0.12 52);
--color-tsb03-accsoft: #f2e4d5;
}
}
@media (prefers-color-scheme: light) {
.tsb-03:has(.tsb-03-theme-input:checked) {
--color-tsb03-bg: #17140f;
--color-tsb03-surf: #1f1b15;
--color-tsb03-surf2: #292319;
--color-tsb03-line: #3a3226;
--color-tsb03-ink: #f2ece1;
--color-tsb03-mut: #a89b87;
--color-tsb03-acc: #e6a06a;
--color-tsb03-acc: oklch(78% 0.11 55);
--color-tsb03-accsoft: #35291d;
}
}<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=Source+Serif+4:opsz,wght@8..60,400;8..60,600;8..60,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=Source+Serif+4:opsz,wght@8..60,400;8..60,600;8..60,700&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
.content[inert] { opacity: .45 }becomesinert:opacity-45— v4 ships the variant, so the off state is declared on the element that goes off.- The right-edge drawer states both positions on itself:
translate-x-fullresting,group-data-open:translate-x-0open. Mirroring the demo to the left edge is two character-level edits. - The 46rem media query is not forced onto Tailwind's
md:--breakpoint-r46in@thememints anr46:variant, so the port keeps the original number. overscroll-behaviorandclip-path: inset(50%)stay arbitrary properties — neither is a design-scale value, and Tailwind is right not to ship them.- The
inertfallback stays in script. Where the attribute is unsupported theinert:variant simply never matches, so the background is switched off without being dimmed — the safe direction to fail. - The scrim and drawer are siblings of the inert subtree, not children. That is a markup rule the class list cannot enforce, and the most common way this pattern is broken.
Here's a working CSS demo from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Tailwind Inert Background Content Behind a Drawer
Source: https://codefronts.com/navigation/tailwind-css-sidebars/tailwind-inert-background-content-behind-a-drawer/
The half of modal behaviour a focus trap cannot give you: everything behind the drawer is switched off with one attribute. inert takes the background out of the accessibility tree, out of the tab order and out of pointer events at once — and because Tailwind v4 ships an inert: variant, the visual consequence (a dimmed table) is declared on the element that goes inert, next to its normal state.
## HTML
```html
<section class="tsb-03 block relative w-full min-h-svh bg-tsb03-bg text-tsb03-ink font-tsb03-sans leading-[1.55]" aria-label="Sable order desk">
<div class="absolute top-3.5 right-3.5 z-60">
<input class="tsb-03-theme-input peer absolute inset-0 w-full h-full m-0 opacity-0 cursor-pointer" type="checkbox" id="tsb-03-theme">
<label class="grid place-items-center w-11 h-11 rounded-full border border-tsb03-line bg-tsb03-surf text-tsb03-ink cursor-pointer peer-focus-visible:outline-2 peer-focus-visible:outline-tsb03-acc peer-focus-visible:outline-offset-2" for="tsb-03-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 relative overflow-hidden min-h-svh bg-tsb03-surf">
<div class="min-w-0 transition-opacity duration-200 inert:opacity-45 motion-reduce:transition-none" id="tsb-03-content">
<div class="flex flex-wrap items-center gap-1.5 py-2.5 pl-4 pr-[4.2rem] bg-tsb03-surf2 border-b border-tsb03-line">
<p class="mr-auto m-0 font-tsb03-display text-[1.05rem]">Sable — order desk</p>
<a class="inline-flex items-center min-h-11 px-2.5 text-[0.88rem] font-semibold text-tsb03-acc no-underline hover:underline focus-visible:outline-2 focus-visible:outline-tsb03-acc focus-visible:outline-offset-2" href="#tsb-03-content">Fulfilment</a>
<a class="inline-flex items-center min-h-11 px-2.5 text-[0.88rem] font-semibold text-tsb03-acc no-underline hover:underline focus-visible:outline-2 focus-visible:outline-tsb03-acc focus-visible:outline-offset-2" href="#tsb-03-content">Returns</a>
<button class="min-h-11 px-4 border border-tsb03-acc bg-tsb03-acc text-tsb03-surf font-tsb03-sans text-[0.9rem] font-semibold cursor-pointer focus-visible:outline-2 focus-visible:outline-tsb03-ink focus-visible:outline-offset-2" type="button" id="tsb-03-open" aria-expanded="false" aria-controls="tsb-03-drawer">Filters</button>
</div>
<table class="w-full border-collapse text-[0.9rem]">
<thead><tr class="[&>th]:px-4 [&>th]:py-3 [&>th]:text-start [&>th]:border-b [&>th]:border-tsb03-line [&>th]:text-[0.74rem] [&>th]:tracking-[0.06em] [&>th]:uppercase [&>th]:font-semibold [&>th]:text-tsb03-mut"><th scope="col">Order</th><th scope="col">Customer</th><th scope="col">State</th><th scope="col"></th></tr></thead>
<tbody class="[&>tr>td]:px-4 [&>tr>td]:py-3 [&>tr>td]:border-b [&>tr>td]:border-tsb03-line [&>tr>td]:min-w-0">
<tr><td>SB-4417</td><td>Priya Sharma</td><td>Packing</td><td><a class="inline-flex items-center min-h-11 px-2.5 text-[0.88rem] font-semibold text-tsb03-acc no-underline hover:underline focus-visible:outline-2 focus-visible:outline-tsb03-acc focus-visible:outline-offset-2" href="#tsb-03-content">Open</a></td></tr>
<tr><td>SB-4418</td><td>Jordan Lee</td><td>Awaiting stock</td><td><a class="inline-flex items-center min-h-11 px-2.5 text-[0.88rem] font-semibold text-tsb03-acc no-underline hover:underline focus-visible:outline-2 focus-visible:outline-tsb03-acc focus-visible:outline-offset-2" href="#tsb-03-content">Open</a></td></tr>
<tr><td>SB-4421</td><td>Sam Rivera</td><td>Ready to ship</td><td><a class="inline-flex items-center min-h-11 px-2.5 text-[0.88rem] font-semibold text-tsb03-acc no-underline hover:underline focus-visible:outline-2 focus-visible:outline-tsb03-acc focus-visible:outline-offset-2" href="#tsb-03-content">Open</a></td></tr>
<tr><td>SB-4423</td><td>Ada Okafor</td><td>Packing</td><td><a class="inline-flex items-center min-h-11 px-2.5 text-[0.88rem] font-semibold text-tsb03-acc no-underline hover:underline focus-visible:outline-2 focus-visible:outline-tsb03-acc focus-visible:outline-offset-2" href="#tsb-03-content">Open</a></td></tr>
<tr><td>SB-4425</td><td>Tomas Vidal</td><td>Awaiting stock</td><td><a class="inline-flex items-center min-h-11 px-2.5 text-[0.88rem] font-semibold text-tsb03-acc no-underline hover:underline focus-visible:outline-2 focus-visible:outline-tsb03-acc focus-visible:outline-offset-2" href="#tsb-03-content">Open</a></td></tr>
</tbody>
</table>
</div>
<div class="absolute inset-0 z-20 bg-[oklch(25%_0.02_60/0.42)] [&[hidden]]:hidden" data-tsb-03-scrim hidden></div>
<aside class="absolute inset-y-0 right-0 z-30 flex flex-col w-[min(18rem,84%)] bg-tsb03-surf border-l border-tsb03-line translate-x-full transition-transform duration-[220ms] ease-out group-data-open:translate-x-0 motion-reduce:duration-[1ms] [&[hidden]]:hidden" id="tsb-03-drawer" role="dialog" aria-modal="true" aria-labelledby="tsb-03-dh" hidden>
<header class="flex items-center gap-2 px-3 py-2.5 border-b border-tsb03-line">
<h3 class="mr-auto m-0 font-tsb03-display text-[1.1rem]" id="tsb-03-dh">Filters</h3>
<button class="grid place-items-center w-11 h-11 border border-tsb03-line bg-transparent text-tsb03-ink cursor-pointer [&>svg]:w-4 [&>svg]:h-4 focus-visible:outline-2 focus-visible:outline-tsb03-acc focus-visible:-outline-offset-2" type="button" data-tsb-03-close>
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M6 6l12 12M18 6L6 18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>
<span class="absolute w-px h-px overflow-hidden whitespace-nowrap [clip-path:inset(50%)]">Close filters</span>
</button>
</header>
<nav class="flex-1 min-h-0 overflow-y-auto [overscroll-behavior:contain]" aria-label="Order views">
<ul class="list-none m-0 p-0">
<li><a class="flex items-center min-w-0 min-h-11 px-3 py-2 text-[0.9rem] text-tsb03-ink no-underline border-b border-tsb03-line hover:bg-tsb03-surf2 focus-visible:outline-2 focus-visible:outline-tsb03-acc focus-visible:-outline-offset-2 aria-[current=page]:bg-tsb03-accsoft aria-[current=page]:text-tsb03-acc aria-[current=page]:font-semibold" href="#tsb-03-content" aria-current="page">All open orders</a></li>
<li><a class="flex items-center min-w-0 min-h-11 px-3 py-2 text-[0.9rem] text-tsb03-ink no-underline border-b border-tsb03-line hover:bg-tsb03-surf2 focus-visible:outline-2 focus-visible:outline-tsb03-acc focus-visible:-outline-offset-2" href="#tsb-03-content">Awaiting stock</a></li>
<li><a class="flex items-center min-w-0 min-h-11 px-3 py-2 text-[0.9rem] text-tsb03-ink no-underline border-b border-tsb03-line hover:bg-tsb03-surf2 focus-visible:outline-2 focus-visible:outline-tsb03-acc focus-visible:-outline-offset-2" href="#tsb-03-content">Ready to ship</a></li>
<li><a class="flex items-center min-w-0 min-h-11 px-3 py-2 text-[0.9rem] text-tsb03-ink no-underline border-b border-tsb03-line hover:bg-tsb03-surf2 focus-visible:outline-2 focus-visible:outline-tsb03-acc focus-visible:-outline-offset-2" href="#tsb-03-content">Returns queue</a></li>
<li><a class="flex items-center min-w-0 min-h-11 px-3 py-2 text-[0.9rem] text-tsb03-ink no-underline border-b border-tsb03-line hover:bg-tsb03-surf2 focus-visible:outline-2 focus-visible:outline-tsb03-acc focus-visible:-outline-offset-2" href="#tsb-03-content">Archived</a></li>
</ul>
</nav>
</aside>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tsb03-bg: #f6f1e8;
--color-tsb03-bg: oklch(95% 0.018 85);
--color-tsb03-surf: #fffdf9;
--color-tsb03-surf: oklch(99% 0.008 85);
--color-tsb03-surf2: #efe7da;
--color-tsb03-surf2: oklch(92% 0.022 80);
--color-tsb03-line: #ddd1bd;
--color-tsb03-line: oklch(85% 0.032 80);
--color-tsb03-ink: #23201a;
--color-tsb03-ink: oklch(24% 0.012 70);
--color-tsb03-mut: #6d6455;
--color-tsb03-mut: oklch(48% 0.02 75);
--color-tsb03-acc: #8a4b1f;
--color-tsb03-acc: oklch(48% 0.12 52);
--color-tsb03-accsoft: #f2e4d5;
--color-tsb03-accsoft: oklch(92% 0.03 60);
--font-tsb03-sans: system-ui, -apple-system, 'Segoe UI', sans-serif;
--font-tsb03-display: 'Source Serif 4', 'Iowan Old Style', Palatino, Georgia, serif;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's
w-full min-h-svh. It scopes the demo, and it holds the stage at full height in the
moment before the browser build has compiled its utilities. */
.tsb-03 {
width: 100%;
min-height: 100svh;
}
.tsb-03 *,
.tsb-03 *::before,
.tsb-03 *::after {
box-sizing: border-box;
}
/* Token flip for the pure-CSS theme toggle, plus the two mandatory mirror blocks. */
.tsb-03:has(.tsb-03-theme-input:checked) {
--color-tsb03-bg: #17140f;
--color-tsb03-surf: #1f1b15;
--color-tsb03-surf2: #292319;
--color-tsb03-line: #3a3226;
--color-tsb03-ink: #f2ece1;
--color-tsb03-mut: #a89b87;
--color-tsb03-acc: #e6a06a;
--color-tsb03-acc: oklch(78% 0.11 55);
--color-tsb03-accsoft: #35291d;
}
@media (prefers-color-scheme: dark) {
.tsb-03 {
--color-tsb03-bg: #17140f;
--color-tsb03-surf: #1f1b15;
--color-tsb03-surf2: #292319;
--color-tsb03-line: #3a3226;
--color-tsb03-ink: #f2ece1;
--color-tsb03-mut: #a89b87;
--color-tsb03-acc: #e6a06a;
--color-tsb03-acc: oklch(78% 0.11 55);
--color-tsb03-accsoft: #35291d;
}
.tsb-03:has(.tsb-03-theme-input:checked) {
--color-tsb03-bg: #f6f1e8;
--color-tsb03-surf: #fffdf9;
--color-tsb03-surf2: #efe7da;
--color-tsb03-line: #ddd1bd;
--color-tsb03-ink: #23201a;
--color-tsb03-mut: #6d6455;
--color-tsb03-acc: #8a4b1f;
--color-tsb03-acc: oklch(48% 0.12 52);
--color-tsb03-accsoft: #f2e4d5;
}
}
@media (prefers-color-scheme: light) {
.tsb-03:has(.tsb-03-theme-input:checked) {
--color-tsb03-bg: #17140f;
--color-tsb03-surf: #1f1b15;
--color-tsb03-surf2: #292319;
--color-tsb03-line: #3a3226;
--color-tsb03-ink: #f2ece1;
--color-tsb03-mut: #a89b87;
--color-tsb03-acc: #e6a06a;
--color-tsb03-acc: oklch(78% 0.11 55);
--color-tsb03-accsoft: #35291d;
}
}
```
## JavaScript
```js
(function () {
document.querySelectorAll('.tsb-03').forEach(function (root) {
if (root.dataset.bound) return; root.dataset.bound = '1';
var stage = root.querySelector('.group'),
drawer = root.querySelector('#tsb-03-drawer'),
scrim = root.querySelector('[data-tsb-03-scrim]'),
content = root.querySelector('#tsb-03-content'),
openBtn = root.querySelector('#tsb-03-open'),
supported = 'inert' in HTMLElement.prototype,
returnTo = null;
function behind() { return Array.prototype.slice.call(content.querySelectorAll('a[href], button:not([disabled])')); }
function block(on) {
if (supported) { content.inert = on; return; }
content.setAttribute('aria-hidden', String(on));
behind().forEach(function (el) { on ? el.setAttribute('tabindex', '-1') : el.removeAttribute('tabindex'); });
}
function onKey(e) { if (e.key === 'Escape') setOpen(false); }
function setOpen(next) {
if (next) returnTo = document.activeElement;
drawer.hidden = scrim.hidden = !next;
stage.toggleAttribute('data-open', next);
openBtn.setAttribute('aria-expanded', String(next));
block(next);
if (next) {
document.addEventListener('keydown', onKey);
requestAnimationFrame(function () { drawer.querySelector('[data-tsb-03-close]').focus(); });
} else {
document.removeEventListener('keydown', onKey);
(returnTo || openBtn).focus({ preventScroll: true });
}
}
openBtn.addEventListener('click', function () { setOpen(true); });
scrim.addEventListener('click', function () { setOpen(false); });
root.querySelectorAll('[data-tsb-03-close]').forEach(function (b) { b.addEventListener('click', function () { setOpen(false); }); });
root.__tsbCleanup = function () { document.removeEventListener('keydown', onKey); };
});
})();
```Here's a working CSS demo from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Tailwind Inert Background Content Behind a Drawer
Source: https://codefronts.com/navigation/tailwind-css-sidebars/tailwind-inert-background-content-behind-a-drawer/
The half of modal behaviour a focus trap cannot give you: everything behind the drawer is switched off with one attribute. inert takes the background out of the accessibility tree, out of the tab order and out of pointer events at once — and because Tailwind v4 ships an inert: variant, the visual consequence (a dimmed table) is declared on the element that goes inert, next to its normal state.
## HTML
```html
<section class="tsb-03 block relative w-full min-h-svh bg-tsb03-bg text-tsb03-ink font-tsb03-sans leading-[1.55]" aria-label="Sable order desk">
<div class="absolute top-3.5 right-3.5 z-60">
<input class="tsb-03-theme-input peer absolute inset-0 w-full h-full m-0 opacity-0 cursor-pointer" type="checkbox" id="tsb-03-theme">
<label class="grid place-items-center w-11 h-11 rounded-full border border-tsb03-line bg-tsb03-surf text-tsb03-ink cursor-pointer peer-focus-visible:outline-2 peer-focus-visible:outline-tsb03-acc peer-focus-visible:outline-offset-2" for="tsb-03-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 relative overflow-hidden min-h-svh bg-tsb03-surf">
<div class="min-w-0 transition-opacity duration-200 inert:opacity-45 motion-reduce:transition-none" id="tsb-03-content">
<div class="flex flex-wrap items-center gap-1.5 py-2.5 pl-4 pr-[4.2rem] bg-tsb03-surf2 border-b border-tsb03-line">
<p class="mr-auto m-0 font-tsb03-display text-[1.05rem]">Sable — order desk</p>
<a class="inline-flex items-center min-h-11 px-2.5 text-[0.88rem] font-semibold text-tsb03-acc no-underline hover:underline focus-visible:outline-2 focus-visible:outline-tsb03-acc focus-visible:outline-offset-2" href="#tsb-03-content">Fulfilment</a>
<a class="inline-flex items-center min-h-11 px-2.5 text-[0.88rem] font-semibold text-tsb03-acc no-underline hover:underline focus-visible:outline-2 focus-visible:outline-tsb03-acc focus-visible:outline-offset-2" href="#tsb-03-content">Returns</a>
<button class="min-h-11 px-4 border border-tsb03-acc bg-tsb03-acc text-tsb03-surf font-tsb03-sans text-[0.9rem] font-semibold cursor-pointer focus-visible:outline-2 focus-visible:outline-tsb03-ink focus-visible:outline-offset-2" type="button" id="tsb-03-open" aria-expanded="false" aria-controls="tsb-03-drawer">Filters</button>
</div>
<table class="w-full border-collapse text-[0.9rem]">
<thead><tr class="[&>th]:px-4 [&>th]:py-3 [&>th]:text-start [&>th]:border-b [&>th]:border-tsb03-line [&>th]:text-[0.74rem] [&>th]:tracking-[0.06em] [&>th]:uppercase [&>th]:font-semibold [&>th]:text-tsb03-mut"><th scope="col">Order</th><th scope="col">Customer</th><th scope="col">State</th><th scope="col"></th></tr></thead>
<tbody class="[&>tr>td]:px-4 [&>tr>td]:py-3 [&>tr>td]:border-b [&>tr>td]:border-tsb03-line [&>tr>td]:min-w-0">
<tr><td>SB-4417</td><td>Priya Sharma</td><td>Packing</td><td><a class="inline-flex items-center min-h-11 px-2.5 text-[0.88rem] font-semibold text-tsb03-acc no-underline hover:underline focus-visible:outline-2 focus-visible:outline-tsb03-acc focus-visible:outline-offset-2" href="#tsb-03-content">Open</a></td></tr>
<tr><td>SB-4418</td><td>Jordan Lee</td><td>Awaiting stock</td><td><a class="inline-flex items-center min-h-11 px-2.5 text-[0.88rem] font-semibold text-tsb03-acc no-underline hover:underline focus-visible:outline-2 focus-visible:outline-tsb03-acc focus-visible:outline-offset-2" href="#tsb-03-content">Open</a></td></tr>
<tr><td>SB-4421</td><td>Sam Rivera</td><td>Ready to ship</td><td><a class="inline-flex items-center min-h-11 px-2.5 text-[0.88rem] font-semibold text-tsb03-acc no-underline hover:underline focus-visible:outline-2 focus-visible:outline-tsb03-acc focus-visible:outline-offset-2" href="#tsb-03-content">Open</a></td></tr>
<tr><td>SB-4423</td><td>Ada Okafor</td><td>Packing</td><td><a class="inline-flex items-center min-h-11 px-2.5 text-[0.88rem] font-semibold text-tsb03-acc no-underline hover:underline focus-visible:outline-2 focus-visible:outline-tsb03-acc focus-visible:outline-offset-2" href="#tsb-03-content">Open</a></td></tr>
<tr><td>SB-4425</td><td>Tomas Vidal</td><td>Awaiting stock</td><td><a class="inline-flex items-center min-h-11 px-2.5 text-[0.88rem] font-semibold text-tsb03-acc no-underline hover:underline focus-visible:outline-2 focus-visible:outline-tsb03-acc focus-visible:outline-offset-2" href="#tsb-03-content">Open</a></td></tr>
</tbody>
</table>
</div>
<div class="absolute inset-0 z-20 bg-[oklch(25%_0.02_60/0.42)] [&[hidden]]:hidden" data-tsb-03-scrim hidden></div>
<aside class="absolute inset-y-0 right-0 z-30 flex flex-col w-[min(18rem,84%)] bg-tsb03-surf border-l border-tsb03-line translate-x-full transition-transform duration-[220ms] ease-out group-data-open:translate-x-0 motion-reduce:duration-[1ms] [&[hidden]]:hidden" id="tsb-03-drawer" role="dialog" aria-modal="true" aria-labelledby="tsb-03-dh" hidden>
<header class="flex items-center gap-2 px-3 py-2.5 border-b border-tsb03-line">
<h3 class="mr-auto m-0 font-tsb03-display text-[1.1rem]" id="tsb-03-dh">Filters</h3>
<button class="grid place-items-center w-11 h-11 border border-tsb03-line bg-transparent text-tsb03-ink cursor-pointer [&>svg]:w-4 [&>svg]:h-4 focus-visible:outline-2 focus-visible:outline-tsb03-acc focus-visible:-outline-offset-2" type="button" data-tsb-03-close>
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M6 6l12 12M18 6L6 18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>
<span class="absolute w-px h-px overflow-hidden whitespace-nowrap [clip-path:inset(50%)]">Close filters</span>
</button>
</header>
<nav class="flex-1 min-h-0 overflow-y-auto [overscroll-behavior:contain]" aria-label="Order views">
<ul class="list-none m-0 p-0">
<li><a class="flex items-center min-w-0 min-h-11 px-3 py-2 text-[0.9rem] text-tsb03-ink no-underline border-b border-tsb03-line hover:bg-tsb03-surf2 focus-visible:outline-2 focus-visible:outline-tsb03-acc focus-visible:-outline-offset-2 aria-[current=page]:bg-tsb03-accsoft aria-[current=page]:text-tsb03-acc aria-[current=page]:font-semibold" href="#tsb-03-content" aria-current="page">All open orders</a></li>
<li><a class="flex items-center min-w-0 min-h-11 px-3 py-2 text-[0.9rem] text-tsb03-ink no-underline border-b border-tsb03-line hover:bg-tsb03-surf2 focus-visible:outline-2 focus-visible:outline-tsb03-acc focus-visible:-outline-offset-2" href="#tsb-03-content">Awaiting stock</a></li>
<li><a class="flex items-center min-w-0 min-h-11 px-3 py-2 text-[0.9rem] text-tsb03-ink no-underline border-b border-tsb03-line hover:bg-tsb03-surf2 focus-visible:outline-2 focus-visible:outline-tsb03-acc focus-visible:-outline-offset-2" href="#tsb-03-content">Ready to ship</a></li>
<li><a class="flex items-center min-w-0 min-h-11 px-3 py-2 text-[0.9rem] text-tsb03-ink no-underline border-b border-tsb03-line hover:bg-tsb03-surf2 focus-visible:outline-2 focus-visible:outline-tsb03-acc focus-visible:-outline-offset-2" href="#tsb-03-content">Returns queue</a></li>
<li><a class="flex items-center min-w-0 min-h-11 px-3 py-2 text-[0.9rem] text-tsb03-ink no-underline border-b border-tsb03-line hover:bg-tsb03-surf2 focus-visible:outline-2 focus-visible:outline-tsb03-acc focus-visible:-outline-offset-2" href="#tsb-03-content">Archived</a></li>
</ul>
</nav>
</aside>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tsb03-bg: #f6f1e8;
--color-tsb03-bg: oklch(95% 0.018 85);
--color-tsb03-surf: #fffdf9;
--color-tsb03-surf: oklch(99% 0.008 85);
--color-tsb03-surf2: #efe7da;
--color-tsb03-surf2: oklch(92% 0.022 80);
--color-tsb03-line: #ddd1bd;
--color-tsb03-line: oklch(85% 0.032 80);
--color-tsb03-ink: #23201a;
--color-tsb03-ink: oklch(24% 0.012 70);
--color-tsb03-mut: #6d6455;
--color-tsb03-mut: oklch(48% 0.02 75);
--color-tsb03-acc: #8a4b1f;
--color-tsb03-acc: oklch(48% 0.12 52);
--color-tsb03-accsoft: #f2e4d5;
--color-tsb03-accsoft: oklch(92% 0.03 60);
--font-tsb03-sans: system-ui, -apple-system, 'Segoe UI', sans-serif;
--font-tsb03-display: 'Source Serif 4', 'Iowan Old Style', Palatino, Georgia, serif;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's
w-full min-h-svh. It scopes the demo, and it holds the stage at full height in the
moment before the browser build has compiled its utilities. */
.tsb-03 {
width: 100%;
min-height: 100svh;
}
.tsb-03 *,
.tsb-03 *::before,
.tsb-03 *::after {
box-sizing: border-box;
}
/* Token flip for the pure-CSS theme toggle, plus the two mandatory mirror blocks. */
.tsb-03:has(.tsb-03-theme-input:checked) {
--color-tsb03-bg: #17140f;
--color-tsb03-surf: #1f1b15;
--color-tsb03-surf2: #292319;
--color-tsb03-line: #3a3226;
--color-tsb03-ink: #f2ece1;
--color-tsb03-mut: #a89b87;
--color-tsb03-acc: #e6a06a;
--color-tsb03-acc: oklch(78% 0.11 55);
--color-tsb03-accsoft: #35291d;
}
@media (prefers-color-scheme: dark) {
.tsb-03 {
--color-tsb03-bg: #17140f;
--color-tsb03-surf: #1f1b15;
--color-tsb03-surf2: #292319;
--color-tsb03-line: #3a3226;
--color-tsb03-ink: #f2ece1;
--color-tsb03-mut: #a89b87;
--color-tsb03-acc: #e6a06a;
--color-tsb03-acc: oklch(78% 0.11 55);
--color-tsb03-accsoft: #35291d;
}
.tsb-03:has(.tsb-03-theme-input:checked) {
--color-tsb03-bg: #f6f1e8;
--color-tsb03-surf: #fffdf9;
--color-tsb03-surf2: #efe7da;
--color-tsb03-line: #ddd1bd;
--color-tsb03-ink: #23201a;
--color-tsb03-mut: #6d6455;
--color-tsb03-acc: #8a4b1f;
--color-tsb03-acc: oklch(48% 0.12 52);
--color-tsb03-accsoft: #f2e4d5;
}
}
@media (prefers-color-scheme: light) {
.tsb-03:has(.tsb-03-theme-input:checked) {
--color-tsb03-bg: #17140f;
--color-tsb03-surf: #1f1b15;
--color-tsb03-surf2: #292319;
--color-tsb03-line: #3a3226;
--color-tsb03-ink: #f2ece1;
--color-tsb03-mut: #a89b87;
--color-tsb03-acc: #e6a06a;
--color-tsb03-acc: oklch(78% 0.11 55);
--color-tsb03-accsoft: #35291d;
}
}
```
## JavaScript
```js
(function () {
document.querySelectorAll('.tsb-03').forEach(function (root) {
if (root.dataset.bound) return; root.dataset.bound = '1';
var stage = root.querySelector('.group'),
drawer = root.querySelector('#tsb-03-drawer'),
scrim = root.querySelector('[data-tsb-03-scrim]'),
content = root.querySelector('#tsb-03-content'),
openBtn = root.querySelector('#tsb-03-open'),
supported = 'inert' in HTMLElement.prototype,
returnTo = null;
function behind() { return Array.prototype.slice.call(content.querySelectorAll('a[href], button:not([disabled])')); }
function block(on) {
if (supported) { content.inert = on; return; }
content.setAttribute('aria-hidden', String(on));
behind().forEach(function (el) { on ? el.setAttribute('tabindex', '-1') : el.removeAttribute('tabindex'); });
}
function onKey(e) { if (e.key === 'Escape') setOpen(false); }
function setOpen(next) {
if (next) returnTo = document.activeElement;
drawer.hidden = scrim.hidden = !next;
stage.toggleAttribute('data-open', next);
openBtn.setAttribute('aria-expanded', String(next));
block(next);
if (next) {
document.addEventListener('keydown', onKey);
requestAnimationFrame(function () { drawer.querySelector('[data-tsb-03-close]').focus(); });
} else {
document.removeEventListener('keydown', onKey);
(returnTo || openBtn).focus({ preventScroll: true });
}
}
openBtn.addEventListener('click', function () { setOpen(true); });
scrim.addEventListener('click', function () { setOpen(false); });
root.querySelectorAll('[data-tsb-03-close]').forEach(function (b) { b.addEventListener('click', function () { setOpen(false); }); });
root.__tsbCleanup = function () { document.removeEventListener('keydown', onKey); };
});
})();
```How this works
One attribute does four jobs. inert on the background makes it unfocusable, unclickable, unreachable by a screen-reader virtual cursor and unsearchable by find-in-page. That is the part a Tab trap cannot do, and it is why demos 02 and 03 are a pair rather than alternatives.
The styling of the off state moves onto the element. The CSS version writes .content[inert] { opacity: .45 } in the stylesheet. Here:
<div class="min-w-0 transition-opacity duration-200
inert:opacity-45" id="tsb-03-content">so reading the markup tells you what happens when the drawer opens, without going looking for the rule.
The fallback stays in script, because it is behaviour. Where inert is unsupported the script sets aria-hidden and tabindex="-1" on every focusable behind — and the inert: variant simply does not match, so the dimming is skipped, which is the right failure: no dimmed-but-still-clickable state.
Right-edge drawers are a sign flip, nothing more. right-0 plus translate-x-full resting; the focus and inert logic is identical to a left drawer.
Make it yours
- Dim more or less by editing
inert:opacity-45, or drop it entirely —inertstill does its work with no visual change at all. - Move the drawer to the left edge:
right-0→left-0andtranslate-x-full→-translate-x-full. - Narrow the panel with
w-[min(18rem,84%)], the only width in the demo. - Retheme through
--color-tsb03-accin@theme; the button, links, active row and focus rings all read it. - Swap the serif by changing
--font-tsb03-displayand the font<link>insetup. - Add a trap on top of this by lifting the keydown block from demo 02;
inertand a trap are complementary, not alternatives.
Gotchas — read before shipping
inerton an ancestor of the drawer switches the drawer off too. The drawer must be a SIBLING of the inert subtree, never inside it.- Do not set
aria-hiddenandinerttogether on supporting engines —inertalready removes the subtree, and the pair produces duplicate, sometimes contradictory, exposure. - The scrim must not live inside the inert content either, or its click handler never fires.
inert:matches[inert]and its descendants, so a colour utility under it inherits the dimming; keep the variant on the wrapper and let opacity do the compositing once.- Without
[&[hidden]]:hiddenthe closed drawer stays visible, becauseflexbeats the UA[hidden]rule. - Reflowing the background while it is inert (a layout shift, a lazy image) still moves content under the user's cursor. Inert is about interaction, not about freezing layout.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
inert is Baseline 2023 (Chrome 102, Safari 15.5, Firefox 112) and Tailwind v4's own floor is higher, so on any engine that can run the generated sheet the attribute is available. The script still feature-detects and falls back to aria-hidden plus tabindex="-1".