29 / 30
All 30 in 30 Tailwind CSS Sidebar Layouts
- 01Tailwind SaaS Dashboard Sidebar with Native Dark Mode Switch
- 02Tailwind Collapsible Admin Sidebar with Icon Tooltips
- 03Tailwind Documentation Sidebar with Multi-Level :target Tree
- 04Tailwind Responsive Sidebar to Bottom Navigation Switch Layout
- 05Tailwind Analytics Sidebar with CSS Subgrid Column Alignment
- 06Tailwind Animated Collapsible Sidebar with Icon-Only Mode
- 07Tailwind Sticky Sidebar Layout with Scroll-Driven Progress Bar
- 08Tailwind Pure CSS Collapsible Sidebar Drawer without JavaScript
- 09Tailwind Fixed Sidebar Layout with Independently Scrolling Content
- 10Tailwind Off-Canvas Navigation Drawer with the Popover API
- 11Tailwind Right-Aligned Sidebar Layout for Inspector Panels
- 12Tailwind Two-Pane Webmail Layout with List and Reading Pane
- 13Tailwind Dual Sidebar App Shell with Left Nav and Context Panel
- 14Tailwind Mini Icon Rail Sidebar with Anchor-Positioned Active Pill
- 15Tailwind E-Commerce Filter Sidebar with Native Details Accordion
- 16Tailwind Floating Glassmorphism Sidebar with Gradient Hairline
- 17Tailwind Sidebar Menu with Pinned User Profile Footer
- 18Tailwind Push-Content Sidebar Drawer with GPU Transitions
- 19Tailwind Off-Canvas Cart Overlay with Blurred Backdrop Scrim
- 20Tailwind Sidebar with Persistent Collapsed State in localStorage
- 21Tailwind Sidebar with Sticky Search Bar Inside the Rail
- 22Tailwind Section-Grouped Sidebar Navigation with Header Labels
- 23Tailwind Sliding Active Indicator Sidebar with CSS :target
- 24Tailwind Multi-Level Nested Tree Sidebar with Details Branches
- 25Tailwind Tabbed Sidebar with Radio-Driven Panel Switching
- 26Tailwind Onboarding Wizard Sidebar with Step Progress Tracking
- 27Tailwind Resizable Sidebar Layout Using the CSS Resize Property
- 28Tailwind Three-Pane Workspace Shell with Icon Rail and List
- 29Tailwind Adaptive Sidebar Layout Using CSS Container Queries
- 30Tailwind Frosted Glass Floating Sidebar with Animated Aurora
Tailwind Adaptive Sidebar Layout Using CSS Container Queries
A sidebar that responds to its own width, not the viewport. Drag the handle and the nav morphs through three densities — full, labels-only, icon rail — entirely from container queries, which v4 exposes as ordinary variants.
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="tsl-29 w-full h-svh min-h-svh flex font-tsl29-sans bg-tsl29-bg text-tsl29-ink">
<div class="@container/rail relative flex-none w-[264px] min-w-[76px] max-w-[340px] resize-x overflow-auto bg-tsl29-panel border-r border-tsl29-line">
<aside aria-label="Library" class="min-h-full px-2.5 py-4">
<div class="flex items-center gap-2.5 px-2.5 pt-1 pb-4 overflow-hidden @max-[120px]/rail:justify-center @max-[120px]/rail:px-0">
<span aria-hidden="true" class="flex-none w-7 h-7 rounded-[9px] bg-[conic-gradient(from_250deg,var(--color-tsl29-accent),oklch(0.6_0.14_220))]"></span>
<span class="whitespace-nowrap font-bold text-[clamp(13px,6cqi,16px)] @max-[120px]/rail:hidden">Shelfspace</span>
</div>
<nav class="flex flex-col gap-[3px]">
<a href="#" aria-current="page" class="flex items-center gap-[11px] min-h-11 px-3 rounded-[11px] text-sm font-medium whitespace-nowrap text-tsl29-mut no-underline transition-colors duration-[180ms] hover:text-tsl29-ink hover:bg-tsl29-hover aria-[current=page]:text-tsl29-accent aria-[current=page]:bg-tsl29-tint focus-visible:outline-2 focus-visible:outline-tsl29-accent focus-visible:outline-offset-2 @max-[120px]/rail:justify-center @max-[120px]/rail:px-0 @max-[120px]/rail:w-11 @max-[120px]/rail:mx-auto"><svg viewBox="0 0 24 24" aria-hidden="true" class="flex-none w-[19px] h-[19px] fill-none stroke-current stroke-[1.8] [stroke-linecap:round] [stroke-linejoin:round]"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20V2H6.5A2.5 2.5 0 0 0 4 4.5zM4 19.5A2.5 2.5 0 0 0 6.5 22H20v-5"/></svg><span class="@max-[120px]/rail:hidden">Reading now</span><small class="ms-auto rounded-full px-2 py-[3px] text-[11px] font-semibold text-tsl29-mut bg-tsl29-chip @max-[220px]/rail:hidden">3</small></a>
<a href="#" class="flex items-center gap-[11px] min-h-11 px-3 rounded-[11px] text-sm font-medium whitespace-nowrap text-tsl29-mut no-underline transition-colors duration-[180ms] hover:text-tsl29-ink hover:bg-tsl29-hover focus-visible:outline-2 focus-visible:outline-tsl29-accent focus-visible:outline-offset-2 @max-[120px]/rail:justify-center @max-[120px]/rail:px-0 @max-[120px]/rail:w-11 @max-[120px]/rail:mx-auto"><svg viewBox="0 0 24 24" aria-hidden="true" class="flex-none w-[19px] h-[19px] fill-none stroke-current stroke-[1.8] [stroke-linecap:round] [stroke-linejoin:round]"><path d="M6 2h12a2 2 0 0 1 2 2v18l-8-4-8 4V4a2 2 0 0 1 2-2z"/></svg><span class="@max-[120px]/rail:hidden">Saved</span><small class="ms-auto rounded-full px-2 py-[3px] text-[11px] font-semibold text-tsl29-mut bg-tsl29-chip @max-[220px]/rail:hidden">47</small></a>
<a href="#" class="flex items-center gap-[11px] min-h-11 px-3 rounded-[11px] text-sm font-medium whitespace-nowrap text-tsl29-mut no-underline transition-colors duration-[180ms] hover:text-tsl29-ink hover:bg-tsl29-hover focus-visible:outline-2 focus-visible:outline-tsl29-accent focus-visible:outline-offset-2 @max-[120px]/rail:justify-center @max-[120px]/rail:px-0 @max-[120px]/rail:w-11 @max-[120px]/rail:mx-auto"><svg viewBox="0 0 24 24" aria-hidden="true" class="flex-none w-[19px] h-[19px] fill-none stroke-current stroke-[1.8] [stroke-linecap:round] [stroke-linejoin:round]"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3.5 2"/></svg><span class="@max-[120px]/rail:hidden">History</span><small class="ms-auto rounded-full px-2 py-[3px] text-[11px] font-semibold text-tsl29-mut bg-tsl29-chip @max-[220px]/rail:hidden">210</small></a>
<a href="#" class="flex items-center gap-[11px] min-h-11 px-3 rounded-[11px] text-sm font-medium whitespace-nowrap text-tsl29-mut no-underline transition-colors duration-[180ms] hover:text-tsl29-ink hover:bg-tsl29-hover focus-visible:outline-2 focus-visible:outline-tsl29-accent focus-visible:outline-offset-2 @max-[120px]/rail:justify-center @max-[120px]/rail:px-0 @max-[120px]/rail:w-11 @max-[120px]/rail:mx-auto"><svg viewBox="0 0 24 24" aria-hidden="true" class="flex-none w-[19px] h-[19px] fill-none stroke-current stroke-[1.8] [stroke-linecap:round] [stroke-linejoin:round]"><circle cx="12" cy="12" r="3"/><path d="M12 2v3m0 14v3M2 12h3m14 0h3M4.9 4.9l2.1 2.1m10 10 2.1 2.1m0-14.2-2.1 2.1m-10 10-2.1 2.1"/></svg><span class="@max-[120px]/rail:hidden">Settings</span></a>
</nav>
</aside>
<span aria-hidden="true" class="absolute inset-y-0 right-0 w-3.5 pointer-coarse:w-[22px] flex flex-col items-center justify-center gap-[3px] cursor-col-resize opacity-50 hover:opacity-100">
<i class="w-[3px] h-[3px] rounded-full bg-tsl29-mut"></i><i class="w-[3px] h-[3px] rounded-full bg-tsl29-mut"></i><i class="w-[3px] h-[3px] rounded-full bg-tsl29-mut"></i>
</span>
</div>
<main class="flex-1 min-w-0 p-8 overflow-y-auto [scrollbar-gutter:stable]">
<h1 class="text-[22px] font-semibold leading-tight mb-2.5">Reading now</h1>
<p class="max-w-[50ch] text-sm leading-[1.65] text-tsl29-mut text-pretty">Drag the dotted edge. Full → labels-only → icon rail: three densities from container queries — the sidebar reads its OWN width, never the viewport's.</p>
</main>
</section><section class="tsl-29 w-full h-svh min-h-svh flex font-tsl29-sans bg-tsl29-bg text-tsl29-ink">
<div class="@container/rail relative flex-none w-[264px] min-w-[76px] max-w-[340px] resize-x overflow-auto bg-tsl29-panel border-r border-tsl29-line">
<aside aria-label="Library" class="min-h-full px-2.5 py-4">
<div class="flex items-center gap-2.5 px-2.5 pt-1 pb-4 overflow-hidden @max-[120px]/rail:justify-center @max-[120px]/rail:px-0">
<span aria-hidden="true" class="flex-none w-7 h-7 rounded-[9px] bg-[conic-gradient(from_250deg,var(--color-tsl29-accent),oklch(0.6_0.14_220))]"></span>
<span class="whitespace-nowrap font-bold text-[clamp(13px,6cqi,16px)] @max-[120px]/rail:hidden">Shelfspace</span>
</div>
<nav class="flex flex-col gap-[3px]">
<a href="#" aria-current="page" class="flex items-center gap-[11px] min-h-11 px-3 rounded-[11px] text-sm font-medium whitespace-nowrap text-tsl29-mut no-underline transition-colors duration-[180ms] hover:text-tsl29-ink hover:bg-tsl29-hover aria-[current=page]:text-tsl29-accent aria-[current=page]:bg-tsl29-tint focus-visible:outline-2 focus-visible:outline-tsl29-accent focus-visible:outline-offset-2 @max-[120px]/rail:justify-center @max-[120px]/rail:px-0 @max-[120px]/rail:w-11 @max-[120px]/rail:mx-auto"><svg viewBox="0 0 24 24" aria-hidden="true" class="flex-none w-[19px] h-[19px] fill-none stroke-current stroke-[1.8] [stroke-linecap:round] [stroke-linejoin:round]"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20V2H6.5A2.5 2.5 0 0 0 4 4.5zM4 19.5A2.5 2.5 0 0 0 6.5 22H20v-5"/></svg><span class="@max-[120px]/rail:hidden">Reading now</span><small class="ms-auto rounded-full px-2 py-[3px] text-[11px] font-semibold text-tsl29-mut bg-tsl29-chip @max-[220px]/rail:hidden">3</small></a>
<a href="#" class="flex items-center gap-[11px] min-h-11 px-3 rounded-[11px] text-sm font-medium whitespace-nowrap text-tsl29-mut no-underline transition-colors duration-[180ms] hover:text-tsl29-ink hover:bg-tsl29-hover focus-visible:outline-2 focus-visible:outline-tsl29-accent focus-visible:outline-offset-2 @max-[120px]/rail:justify-center @max-[120px]/rail:px-0 @max-[120px]/rail:w-11 @max-[120px]/rail:mx-auto"><svg viewBox="0 0 24 24" aria-hidden="true" class="flex-none w-[19px] h-[19px] fill-none stroke-current stroke-[1.8] [stroke-linecap:round] [stroke-linejoin:round]"><path d="M6 2h12a2 2 0 0 1 2 2v18l-8-4-8 4V4a2 2 0 0 1 2-2z"/></svg><span class="@max-[120px]/rail:hidden">Saved</span><small class="ms-auto rounded-full px-2 py-[3px] text-[11px] font-semibold text-tsl29-mut bg-tsl29-chip @max-[220px]/rail:hidden">47</small></a>
<a href="#" class="flex items-center gap-[11px] min-h-11 px-3 rounded-[11px] text-sm font-medium whitespace-nowrap text-tsl29-mut no-underline transition-colors duration-[180ms] hover:text-tsl29-ink hover:bg-tsl29-hover focus-visible:outline-2 focus-visible:outline-tsl29-accent focus-visible:outline-offset-2 @max-[120px]/rail:justify-center @max-[120px]/rail:px-0 @max-[120px]/rail:w-11 @max-[120px]/rail:mx-auto"><svg viewBox="0 0 24 24" aria-hidden="true" class="flex-none w-[19px] h-[19px] fill-none stroke-current stroke-[1.8] [stroke-linecap:round] [stroke-linejoin:round]"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3.5 2"/></svg><span class="@max-[120px]/rail:hidden">History</span><small class="ms-auto rounded-full px-2 py-[3px] text-[11px] font-semibold text-tsl29-mut bg-tsl29-chip @max-[220px]/rail:hidden">210</small></a>
<a href="#" class="flex items-center gap-[11px] min-h-11 px-3 rounded-[11px] text-sm font-medium whitespace-nowrap text-tsl29-mut no-underline transition-colors duration-[180ms] hover:text-tsl29-ink hover:bg-tsl29-hover focus-visible:outline-2 focus-visible:outline-tsl29-accent focus-visible:outline-offset-2 @max-[120px]/rail:justify-center @max-[120px]/rail:px-0 @max-[120px]/rail:w-11 @max-[120px]/rail:mx-auto"><svg viewBox="0 0 24 24" aria-hidden="true" class="flex-none w-[19px] h-[19px] fill-none stroke-current stroke-[1.8] [stroke-linecap:round] [stroke-linejoin:round]"><circle cx="12" cy="12" r="3"/><path d="M12 2v3m0 14v3M2 12h3m14 0h3M4.9 4.9l2.1 2.1m10 10 2.1 2.1m0-14.2-2.1 2.1m-10 10-2.1 2.1"/></svg><span class="@max-[120px]/rail:hidden">Settings</span></a>
</nav>
</aside>
<span aria-hidden="true" class="absolute inset-y-0 right-0 w-3.5 pointer-coarse:w-[22px] flex flex-col items-center justify-center gap-[3px] cursor-col-resize opacity-50 hover:opacity-100">
<i class="w-[3px] h-[3px] rounded-full bg-tsl29-mut"></i><i class="w-[3px] h-[3px] rounded-full bg-tsl29-mut"></i><i class="w-[3px] h-[3px] rounded-full bg-tsl29-mut"></i>
</span>
</div>
<main class="flex-1 min-w-0 p-8 overflow-y-auto [scrollbar-gutter:stable]">
<h1 class="text-[22px] font-semibold leading-tight mb-2.5">Reading now</h1>
<p class="max-w-[50ch] text-sm leading-[1.65] text-tsl29-mut text-pretty">Drag the dotted edge. Full → labels-only → icon rail: three densities from container queries — the sidebar reads its OWN width, never the viewport's.</p>
</main>
</section>@import "tailwindcss";
@theme {
--color-tsl29-bg: #101613;
--color-tsl29-bg: oklch(0.13 0.012 155);
--color-tsl29-panel: #161f1b;
--color-tsl29-panel: oklch(0.17 0.016 155);
--color-tsl29-ink: #e8f0ec;
--color-tsl29-ink: oklch(0.94 0.005 155);
--color-tsl29-mut: #929d97;
--color-tsl29-mut: oklch(0.64 0.013 155);
--color-tsl29-line: #343f39;
--color-tsl29-line: oklch(0.28 0.017 155);
--color-tsl29-accent: #5fdcae;
--color-tsl29-accent: oklch(0.78 0.15 160);
--color-tsl29-chip: rgb(232 240 236 / 0.08);
--color-tsl29-chip: color-mix(in oklab, oklch(0.94 0.005 155) 8%, transparent);
--color-tsl29-hover: rgb(232 240 236 / 0.06);
--color-tsl29-hover: color-mix(in oklab, oklch(0.94 0.005 155) 6%, transparent);
--color-tsl29-tint: rgb(95 220 174 / 0.14);
--color-tsl29-tint: color-mix(in oklab, oklch(0.78 0.15 160) 14%, transparent);
--font-tsl29-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}
.tsl-29 {
width: 100%;
height: 100svh;
min-height: 100svh;
color-scheme: dark;
}@import "tailwindcss";
@theme {
--color-tsl29-bg: #101613;
--color-tsl29-bg: oklch(0.13 0.012 155);
--color-tsl29-panel: #161f1b;
--color-tsl29-panel: oklch(0.17 0.016 155);
--color-tsl29-ink: #e8f0ec;
--color-tsl29-ink: oklch(0.94 0.005 155);
--color-tsl29-mut: #929d97;
--color-tsl29-mut: oklch(0.64 0.013 155);
--color-tsl29-line: #343f39;
--color-tsl29-line: oklch(0.28 0.017 155);
--color-tsl29-accent: #5fdcae;
--color-tsl29-accent: oklch(0.78 0.15 160);
--color-tsl29-chip: rgb(232 240 236 / 0.08);
--color-tsl29-chip: color-mix(in oklab, oklch(0.94 0.005 155) 8%, transparent);
--color-tsl29-hover: rgb(232 240 236 / 0.06);
--color-tsl29-hover: color-mix(in oklab, oklch(0.94 0.005 155) 6%, transparent);
--color-tsl29-tint: rgb(95 220 174 / 0.14);
--color-tsl29-tint: color-mix(in oklab, oklch(0.78 0.15 160) 14%, transparent);
--font-tsl29-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}
.tsl-29 {
width: 100%;
height: 100svh;
min-height: 100svh;
color-scheme: dark;
}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-tsl29-bg: #101613;
--color-tsl29-bg: oklch(0.13 0.012 155);
--color-tsl29-panel: #161f1b;
--color-tsl29-panel: oklch(0.17 0.016 155);
--color-tsl29-ink: #e8f0ec;
--color-tsl29-ink: oklch(0.94 0.005 155);
--color-tsl29-mut: #929d97;
--color-tsl29-mut: oklch(0.64 0.013 155);
--color-tsl29-line: #343f39;
--color-tsl29-line: oklch(0.28 0.017 155);
--color-tsl29-accent: #5fdcae;
--color-tsl29-accent: oklch(0.78 0.15 160);
--color-tsl29-chip: rgb(232 240 236 / 0.08);
--color-tsl29-chip: color-mix(in oklab, oklch(0.94 0.005 155) 8%, transparent);
--color-tsl29-hover: rgb(232 240 236 / 0.06);
--color-tsl29-hover: color-mix(in oklab, oklch(0.94 0.005 155) 6%, transparent);
--color-tsl29-tint: rgb(95 220 174 / 0.14);
--color-tsl29-tint: color-mix(in oklab, oklch(0.78 0.15 160) 14%, transparent);
--font-tsl29-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}
.tsl-29 {
width: 100%;
height: 100svh;
min-height: 100svh;
color-scheme: dark;
}@import "tailwindcss";
@theme {
--color-tsl29-bg: #101613;
--color-tsl29-bg: oklch(0.13 0.012 155);
--color-tsl29-panel: #161f1b;
--color-tsl29-panel: oklch(0.17 0.016 155);
--color-tsl29-ink: #e8f0ec;
--color-tsl29-ink: oklch(0.94 0.005 155);
--color-tsl29-mut: #929d97;
--color-tsl29-mut: oklch(0.64 0.013 155);
--color-tsl29-line: #343f39;
--color-tsl29-line: oklch(0.28 0.017 155);
--color-tsl29-accent: #5fdcae;
--color-tsl29-accent: oklch(0.78 0.15 160);
--color-tsl29-chip: rgb(232 240 236 / 0.08);
--color-tsl29-chip: color-mix(in oklab, oklch(0.94 0.005 155) 8%, transparent);
--color-tsl29-hover: rgb(232 240 236 / 0.06);
--color-tsl29-hover: color-mix(in oklab, oklch(0.94 0.005 155) 6%, transparent);
--color-tsl29-tint: rgb(95 220 174 / 0.14);
--color-tsl29-tint: color-mix(in oklab, oklch(0.78 0.15 160) 14%, transparent);
--font-tsl29-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}
.tsl-29 {
width: 100%;
height: 100svh;
min-height: 100svh;
color-scheme: dark;
}<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=Plus+Jakarta+Sans:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
- Three
@containerblocks become variant prefixes on the four elements that actually change. The density tiers stop being a separate section of the stylesheet and become a property of each part — which is what makes the component safe to drop into an unfamiliar column. @container/railcompiles tocontainer-type:inline-sizepluscontainer-name, so the wrapper needs one class where the CSS needed a shorthand people routinely mistype.- UI change from the CSS original: collapsed icon links keep
w-11(the original narrowed to 44px only in the icon tier, but rows elsewhere were 44px minimum already), and the count chips now hide at 220px rather than shrinking into an unreadable size.
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 Adaptive Sidebar Layout Using CSS Container Queries
Source: https://codefronts.com/layouts/tailwind-css-sidebar-layouts/tailwind-adaptive-sidebar-css-container-queries/
A sidebar that responds to its own width, not the viewport. Drag the handle and the nav morphs through three densities — full, labels-only, icon rail — entirely from container queries, which v4 exposes as ordinary variants.
## HTML
```html
<section class="tsl-29 w-full h-svh min-h-svh flex font-tsl29-sans bg-tsl29-bg text-tsl29-ink">
<div class="@container/rail relative flex-none w-[264px] min-w-[76px] max-w-[340px] resize-x overflow-auto bg-tsl29-panel border-r border-tsl29-line">
<aside aria-label="Library" class="min-h-full px-2.5 py-4">
<div class="flex items-center gap-2.5 px-2.5 pt-1 pb-4 overflow-hidden @max-[120px]/rail:justify-center @max-[120px]/rail:px-0">
<span aria-hidden="true" class="flex-none w-7 h-7 rounded-[9px] bg-[conic-gradient(from_250deg,var(--color-tsl29-accent),oklch(0.6_0.14_220))]"></span>
<span class="whitespace-nowrap font-bold text-[clamp(13px,6cqi,16px)] @max-[120px]/rail:hidden">Shelfspace</span>
</div>
<nav class="flex flex-col gap-[3px]">
<a href="#" aria-current="page" class="flex items-center gap-[11px] min-h-11 px-3 rounded-[11px] text-sm font-medium whitespace-nowrap text-tsl29-mut no-underline transition-colors duration-[180ms] hover:text-tsl29-ink hover:bg-tsl29-hover aria-[current=page]:text-tsl29-accent aria-[current=page]:bg-tsl29-tint focus-visible:outline-2 focus-visible:outline-tsl29-accent focus-visible:outline-offset-2 @max-[120px]/rail:justify-center @max-[120px]/rail:px-0 @max-[120px]/rail:w-11 @max-[120px]/rail:mx-auto"><svg viewBox="0 0 24 24" aria-hidden="true" class="flex-none w-[19px] h-[19px] fill-none stroke-current stroke-[1.8] [stroke-linecap:round] [stroke-linejoin:round]"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20V2H6.5A2.5 2.5 0 0 0 4 4.5zM4 19.5A2.5 2.5 0 0 0 6.5 22H20v-5"/></svg><span class="@max-[120px]/rail:hidden">Reading now</span><small class="ms-auto rounded-full px-2 py-[3px] text-[11px] font-semibold text-tsl29-mut bg-tsl29-chip @max-[220px]/rail:hidden">3</small></a>
<a href="#" class="flex items-center gap-[11px] min-h-11 px-3 rounded-[11px] text-sm font-medium whitespace-nowrap text-tsl29-mut no-underline transition-colors duration-[180ms] hover:text-tsl29-ink hover:bg-tsl29-hover focus-visible:outline-2 focus-visible:outline-tsl29-accent focus-visible:outline-offset-2 @max-[120px]/rail:justify-center @max-[120px]/rail:px-0 @max-[120px]/rail:w-11 @max-[120px]/rail:mx-auto"><svg viewBox="0 0 24 24" aria-hidden="true" class="flex-none w-[19px] h-[19px] fill-none stroke-current stroke-[1.8] [stroke-linecap:round] [stroke-linejoin:round]"><path d="M6 2h12a2 2 0 0 1 2 2v18l-8-4-8 4V4a2 2 0 0 1 2-2z"/></svg><span class="@max-[120px]/rail:hidden">Saved</span><small class="ms-auto rounded-full px-2 py-[3px] text-[11px] font-semibold text-tsl29-mut bg-tsl29-chip @max-[220px]/rail:hidden">47</small></a>
<a href="#" class="flex items-center gap-[11px] min-h-11 px-3 rounded-[11px] text-sm font-medium whitespace-nowrap text-tsl29-mut no-underline transition-colors duration-[180ms] hover:text-tsl29-ink hover:bg-tsl29-hover focus-visible:outline-2 focus-visible:outline-tsl29-accent focus-visible:outline-offset-2 @max-[120px]/rail:justify-center @max-[120px]/rail:px-0 @max-[120px]/rail:w-11 @max-[120px]/rail:mx-auto"><svg viewBox="0 0 24 24" aria-hidden="true" class="flex-none w-[19px] h-[19px] fill-none stroke-current stroke-[1.8] [stroke-linecap:round] [stroke-linejoin:round]"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3.5 2"/></svg><span class="@max-[120px]/rail:hidden">History</span><small class="ms-auto rounded-full px-2 py-[3px] text-[11px] font-semibold text-tsl29-mut bg-tsl29-chip @max-[220px]/rail:hidden">210</small></a>
<a href="#" class="flex items-center gap-[11px] min-h-11 px-3 rounded-[11px] text-sm font-medium whitespace-nowrap text-tsl29-mut no-underline transition-colors duration-[180ms] hover:text-tsl29-ink hover:bg-tsl29-hover focus-visible:outline-2 focus-visible:outline-tsl29-accent focus-visible:outline-offset-2 @max-[120px]/rail:justify-center @max-[120px]/rail:px-0 @max-[120px]/rail:w-11 @max-[120px]/rail:mx-auto"><svg viewBox="0 0 24 24" aria-hidden="true" class="flex-none w-[19px] h-[19px] fill-none stroke-current stroke-[1.8] [stroke-linecap:round] [stroke-linejoin:round]"><circle cx="12" cy="12" r="3"/><path d="M12 2v3m0 14v3M2 12h3m14 0h3M4.9 4.9l2.1 2.1m10 10 2.1 2.1m0-14.2-2.1 2.1m-10 10-2.1 2.1"/></svg><span class="@max-[120px]/rail:hidden">Settings</span></a>
</nav>
</aside>
<span aria-hidden="true" class="absolute inset-y-0 right-0 w-3.5 pointer-coarse:w-[22px] flex flex-col items-center justify-center gap-[3px] cursor-col-resize opacity-50 hover:opacity-100">
<i class="w-[3px] h-[3px] rounded-full bg-tsl29-mut"></i><i class="w-[3px] h-[3px] rounded-full bg-tsl29-mut"></i><i class="w-[3px] h-[3px] rounded-full bg-tsl29-mut"></i>
</span>
</div>
<main class="flex-1 min-w-0 p-8 overflow-y-auto [scrollbar-gutter:stable]">
<h1 class="text-[22px] font-semibold leading-tight mb-2.5">Reading now</h1>
<p class="max-w-[50ch] text-sm leading-[1.65] text-tsl29-mut text-pretty">Drag the dotted edge. Full → labels-only → icon rail: three densities from container queries — the sidebar reads its OWN width, never the viewport's.</p>
</main>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tsl29-bg: #101613;
--color-tsl29-bg: oklch(0.13 0.012 155);
--color-tsl29-panel: #161f1b;
--color-tsl29-panel: oklch(0.17 0.016 155);
--color-tsl29-ink: #e8f0ec;
--color-tsl29-ink: oklch(0.94 0.005 155);
--color-tsl29-mut: #929d97;
--color-tsl29-mut: oklch(0.64 0.013 155);
--color-tsl29-line: #343f39;
--color-tsl29-line: oklch(0.28 0.017 155);
--color-tsl29-accent: #5fdcae;
--color-tsl29-accent: oklch(0.78 0.15 160);
--color-tsl29-chip: rgb(232 240 236 / 0.08);
--color-tsl29-chip: color-mix(in oklab, oklch(0.94 0.005 155) 8%, transparent);
--color-tsl29-hover: rgb(232 240 236 / 0.06);
--color-tsl29-hover: color-mix(in oklab, oklch(0.94 0.005 155) 6%, transparent);
--color-tsl29-tint: rgb(95 220 174 / 0.14);
--color-tsl29-tint: color-mix(in oklab, oklch(0.78 0.15 160) 14%, transparent);
--font-tsl29-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}
.tsl-29 {
width: 100%;
height: 100svh;
min-height: 100svh;
color-scheme: dark;
}
```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 Adaptive Sidebar Layout Using CSS Container Queries
Source: https://codefronts.com/layouts/tailwind-css-sidebar-layouts/tailwind-adaptive-sidebar-css-container-queries/
A sidebar that responds to its own width, not the viewport. Drag the handle and the nav morphs through three densities — full, labels-only, icon rail — entirely from container queries, which v4 exposes as ordinary variants.
## HTML
```html
<section class="tsl-29 w-full h-svh min-h-svh flex font-tsl29-sans bg-tsl29-bg text-tsl29-ink">
<div class="@container/rail relative flex-none w-[264px] min-w-[76px] max-w-[340px] resize-x overflow-auto bg-tsl29-panel border-r border-tsl29-line">
<aside aria-label="Library" class="min-h-full px-2.5 py-4">
<div class="flex items-center gap-2.5 px-2.5 pt-1 pb-4 overflow-hidden @max-[120px]/rail:justify-center @max-[120px]/rail:px-0">
<span aria-hidden="true" class="flex-none w-7 h-7 rounded-[9px] bg-[conic-gradient(from_250deg,var(--color-tsl29-accent),oklch(0.6_0.14_220))]"></span>
<span class="whitespace-nowrap font-bold text-[clamp(13px,6cqi,16px)] @max-[120px]/rail:hidden">Shelfspace</span>
</div>
<nav class="flex flex-col gap-[3px]">
<a href="#" aria-current="page" class="flex items-center gap-[11px] min-h-11 px-3 rounded-[11px] text-sm font-medium whitespace-nowrap text-tsl29-mut no-underline transition-colors duration-[180ms] hover:text-tsl29-ink hover:bg-tsl29-hover aria-[current=page]:text-tsl29-accent aria-[current=page]:bg-tsl29-tint focus-visible:outline-2 focus-visible:outline-tsl29-accent focus-visible:outline-offset-2 @max-[120px]/rail:justify-center @max-[120px]/rail:px-0 @max-[120px]/rail:w-11 @max-[120px]/rail:mx-auto"><svg viewBox="0 0 24 24" aria-hidden="true" class="flex-none w-[19px] h-[19px] fill-none stroke-current stroke-[1.8] [stroke-linecap:round] [stroke-linejoin:round]"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20V2H6.5A2.5 2.5 0 0 0 4 4.5zM4 19.5A2.5 2.5 0 0 0 6.5 22H20v-5"/></svg><span class="@max-[120px]/rail:hidden">Reading now</span><small class="ms-auto rounded-full px-2 py-[3px] text-[11px] font-semibold text-tsl29-mut bg-tsl29-chip @max-[220px]/rail:hidden">3</small></a>
<a href="#" class="flex items-center gap-[11px] min-h-11 px-3 rounded-[11px] text-sm font-medium whitespace-nowrap text-tsl29-mut no-underline transition-colors duration-[180ms] hover:text-tsl29-ink hover:bg-tsl29-hover focus-visible:outline-2 focus-visible:outline-tsl29-accent focus-visible:outline-offset-2 @max-[120px]/rail:justify-center @max-[120px]/rail:px-0 @max-[120px]/rail:w-11 @max-[120px]/rail:mx-auto"><svg viewBox="0 0 24 24" aria-hidden="true" class="flex-none w-[19px] h-[19px] fill-none stroke-current stroke-[1.8] [stroke-linecap:round] [stroke-linejoin:round]"><path d="M6 2h12a2 2 0 0 1 2 2v18l-8-4-8 4V4a2 2 0 0 1 2-2z"/></svg><span class="@max-[120px]/rail:hidden">Saved</span><small class="ms-auto rounded-full px-2 py-[3px] text-[11px] font-semibold text-tsl29-mut bg-tsl29-chip @max-[220px]/rail:hidden">47</small></a>
<a href="#" class="flex items-center gap-[11px] min-h-11 px-3 rounded-[11px] text-sm font-medium whitespace-nowrap text-tsl29-mut no-underline transition-colors duration-[180ms] hover:text-tsl29-ink hover:bg-tsl29-hover focus-visible:outline-2 focus-visible:outline-tsl29-accent focus-visible:outline-offset-2 @max-[120px]/rail:justify-center @max-[120px]/rail:px-0 @max-[120px]/rail:w-11 @max-[120px]/rail:mx-auto"><svg viewBox="0 0 24 24" aria-hidden="true" class="flex-none w-[19px] h-[19px] fill-none stroke-current stroke-[1.8] [stroke-linecap:round] [stroke-linejoin:round]"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3.5 2"/></svg><span class="@max-[120px]/rail:hidden">History</span><small class="ms-auto rounded-full px-2 py-[3px] text-[11px] font-semibold text-tsl29-mut bg-tsl29-chip @max-[220px]/rail:hidden">210</small></a>
<a href="#" class="flex items-center gap-[11px] min-h-11 px-3 rounded-[11px] text-sm font-medium whitespace-nowrap text-tsl29-mut no-underline transition-colors duration-[180ms] hover:text-tsl29-ink hover:bg-tsl29-hover focus-visible:outline-2 focus-visible:outline-tsl29-accent focus-visible:outline-offset-2 @max-[120px]/rail:justify-center @max-[120px]/rail:px-0 @max-[120px]/rail:w-11 @max-[120px]/rail:mx-auto"><svg viewBox="0 0 24 24" aria-hidden="true" class="flex-none w-[19px] h-[19px] fill-none stroke-current stroke-[1.8] [stroke-linecap:round] [stroke-linejoin:round]"><circle cx="12" cy="12" r="3"/><path d="M12 2v3m0 14v3M2 12h3m14 0h3M4.9 4.9l2.1 2.1m10 10 2.1 2.1m0-14.2-2.1 2.1m-10 10-2.1 2.1"/></svg><span class="@max-[120px]/rail:hidden">Settings</span></a>
</nav>
</aside>
<span aria-hidden="true" class="absolute inset-y-0 right-0 w-3.5 pointer-coarse:w-[22px] flex flex-col items-center justify-center gap-[3px] cursor-col-resize opacity-50 hover:opacity-100">
<i class="w-[3px] h-[3px] rounded-full bg-tsl29-mut"></i><i class="w-[3px] h-[3px] rounded-full bg-tsl29-mut"></i><i class="w-[3px] h-[3px] rounded-full bg-tsl29-mut"></i>
</span>
</div>
<main class="flex-1 min-w-0 p-8 overflow-y-auto [scrollbar-gutter:stable]">
<h1 class="text-[22px] font-semibold leading-tight mb-2.5">Reading now</h1>
<p class="max-w-[50ch] text-sm leading-[1.65] text-tsl29-mut text-pretty">Drag the dotted edge. Full → labels-only → icon rail: three densities from container queries — the sidebar reads its OWN width, never the viewport's.</p>
</main>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tsl29-bg: #101613;
--color-tsl29-bg: oklch(0.13 0.012 155);
--color-tsl29-panel: #161f1b;
--color-tsl29-panel: oklch(0.17 0.016 155);
--color-tsl29-ink: #e8f0ec;
--color-tsl29-ink: oklch(0.94 0.005 155);
--color-tsl29-mut: #929d97;
--color-tsl29-mut: oklch(0.64 0.013 155);
--color-tsl29-line: #343f39;
--color-tsl29-line: oklch(0.28 0.017 155);
--color-tsl29-accent: #5fdcae;
--color-tsl29-accent: oklch(0.78 0.15 160);
--color-tsl29-chip: rgb(232 240 236 / 0.08);
--color-tsl29-chip: color-mix(in oklab, oklch(0.94 0.005 155) 8%, transparent);
--color-tsl29-hover: rgb(232 240 236 / 0.06);
--color-tsl29-hover: color-mix(in oklab, oklch(0.94 0.005 155) 6%, transparent);
--color-tsl29-tint: rgb(95 220 174 / 0.14);
--color-tsl29-tint: color-mix(in oklab, oklch(0.78 0.15 160) 14%, transparent);
--font-tsl29-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}
.tsl-29 {
width: 100%;
height: 100svh;
min-height: 100svh;
color-scheme: dark;
}
```How this works
Container queries are the best-fitting feature in the whole port. @max-[120px]/rail:w-11 reads exactly like md:w-11, so a component that adapts to its own column costs no more authoring effort than one that adapts to the viewport — which is precisely the barrier that kept container queries niche for two years.
The same file serves three contexts: a full app shell, a narrow split-view column, a cramped embedded widget. No props, no JavaScript, no viewport assumptions. The density thresholds (220px, 120px) are the component's public API and should be documented like props.
Make it yours
- Add an ultra-wide tier with
@min-[320px]/rail:that reveals recent-item sublists. cqiunits scale anything with the container — tryp-[calc(8px+2cqi)]for fluid density.- In production the resize wrapper disappears: your real grid tracks or split panes become the containers.
Gotchas — read before shipping
- A container cannot size itself by its contents in the queried axis — which is exactly why the WRAPPER carries the resize, not the aside.
- Always name the container. Unnamed
@max-*variants bind to the nearest ancestor container, wherever that happens to be. - Container queries style descendants only; the container cannot respond to its own query.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
Size container queries and cqi units have been Baseline since Feb 2023 — fully production-ready.