07 / 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 Sticky Sidebar Layout with Scroll-Driven Progress Bar
The long-article companion rail: a TOC that rides along as you scroll, with the three constraints people always miss, plus a scroll-driven reading progress bar. The port is a good illustration of the split — sticky is three utilities, the scroll timeline stays CSS because it has no utility form and should not.
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-07 w-full h-svh min-h-svh overflow-y-auto font-tsl07-sans bg-tsl07-bg text-tsl07-ink">
<div aria-hidden="true" class="sticky top-0 z-10 h-[3px] w-0 bg-[linear-gradient(90deg,var(--color-tsl07-accent),oklch(0.65_0.15_300))] animate-tsl07-grow motion-reduce:animate-none motion-reduce:w-full motion-reduce:opacity-25"></div>
<div class="grid grid-cols-[230px_minmax(0,1fr)] gap-10 max-w-[920px] mx-auto px-6 pt-8 pb-16">
<aside aria-label="On this page" class="sticky top-6 self-start max-h-[calc(100svh-3rem)] overflow-y-auto border-r border-tsl07-line pr-5">
<h2 class="mb-3 text-[11px] font-semibold uppercase tracking-[0.09em] text-tsl07-mut">On this page</h2>
<nav class="flex flex-col gap-0.5">
<a href="#tsl07-s1" aria-current="true" class="px-2.5 py-[7px] rounded-lg text-[13px] font-medium leading-snug no-underline text-tsl07-mut transition-colors duration-150 hover:text-tsl07-ink hover:bg-tsl07-hover aria-[current=true]:text-tsl07-accent aria-[current=true]:bg-tsl07-tint aria-[current=true]:font-semibold focus-visible:outline-2 focus-visible:outline-tsl07-accent focus-visible:outline-offset-1">Why grids beat floats</a>
<a href="#tsl07-s2" class="px-2.5 py-[7px] rounded-lg text-[13px] font-medium leading-snug no-underline text-tsl07-mut transition-colors duration-150 hover:text-tsl07-ink hover:bg-tsl07-hover focus-visible:outline-2 focus-visible:outline-tsl07-accent focus-visible:outline-offset-1">Track sizing deep-dive</a>
<a href="#tsl07-s3" class="px-2.5 py-[7px] rounded-lg text-[13px] font-medium leading-snug no-underline text-tsl07-mut transition-colors duration-150 hover:text-tsl07-ink hover:bg-tsl07-hover focus-visible:outline-2 focus-visible:outline-tsl07-accent focus-visible:outline-offset-1">Alignment properties</a>
<a href="#tsl07-s4" class="px-2.5 py-[7px] rounded-lg text-[13px] font-medium leading-snug no-underline text-tsl07-mut transition-colors duration-150 hover:text-tsl07-ink hover:bg-tsl07-hover focus-visible:outline-2 focus-visible:outline-tsl07-accent focus-visible:outline-offset-1">Subgrid in practice</a>
<a href="#tsl07-s5" class="px-2.5 py-[7px] rounded-lg text-[13px] font-medium leading-snug no-underline text-tsl07-mut transition-colors duration-150 hover:text-tsl07-ink hover:bg-tsl07-hover focus-visible:outline-2 focus-visible:outline-tsl07-accent focus-visible:outline-offset-1">Container queries</a>
</nav>
</aside>
<article>
<h1 id="tsl07-s1" class="scroll-mt-5 text-[26px] font-bold leading-tight tracking-tight mb-3.5">Modern CSS layout, properly</h1>
<p class="max-w-[62ch] text-[14.5px] leading-[1.75] text-tsl07-body text-pretty">Scroll this page — the rail sticks 24px from the top while the article flows past. The bar up top is a scroll-driven animation, no JS.</p>
<h2 id="tsl07-s2" class="scroll-mt-5 mt-[30px] mb-2.5 text-lg font-bold leading-snug">Track sizing deep-dive</h2>
<p class="max-w-[62ch] text-[14.5px] leading-[1.75] text-tsl07-body text-pretty">Fixed, fractional, minmax and fit-content tracks each answer a different layout question. Fractional units distribute leftover space, not total space — the distinction that explains most grid surprises.</p>
<h2 id="tsl07-s3" class="scroll-mt-5 mt-[30px] mb-2.5 text-lg font-bold leading-snug">Alignment properties</h2>
<p class="max-w-[62ch] text-[14.5px] leading-[1.75] text-tsl07-body text-pretty">justify- controls the inline axis, align- the block axis. The -content properties move tracks; -items move children within their cells; -self overrides per child.</p>
<h2 id="tsl07-s4" class="scroll-mt-5 mt-[30px] mb-2.5 text-lg font-bold leading-snug">Subgrid in practice</h2>
<p class="max-w-[62ch] text-[14.5px] leading-[1.75] text-tsl07-body text-pretty">Subgrid lets nested elements adopt parent rails — card grids with aligned footers, forms with aligned labels, sidebars with aligned badges (see demo 05).</p>
<h2 id="tsl07-s5" class="scroll-mt-5 mt-[30px] mb-2.5 text-lg font-bold leading-snug">Container queries</h2>
<p class="max-w-[62ch] text-[14.5px] leading-[1.75] text-tsl07-body text-pretty">Components respond to their container, not the viewport. Demo 29 rebuilds this sidebar pattern on container queries.</p>
</article>
</div>
</section><section class="tsl-07 w-full h-svh min-h-svh overflow-y-auto font-tsl07-sans bg-tsl07-bg text-tsl07-ink">
<div aria-hidden="true" class="sticky top-0 z-10 h-[3px] w-0 bg-[linear-gradient(90deg,var(--color-tsl07-accent),oklch(0.65_0.15_300))] animate-tsl07-grow motion-reduce:animate-none motion-reduce:w-full motion-reduce:opacity-25"></div>
<div class="grid grid-cols-[230px_minmax(0,1fr)] gap-10 max-w-[920px] mx-auto px-6 pt-8 pb-16">
<aside aria-label="On this page" class="sticky top-6 self-start max-h-[calc(100svh-3rem)] overflow-y-auto border-r border-tsl07-line pr-5">
<h2 class="mb-3 text-[11px] font-semibold uppercase tracking-[0.09em] text-tsl07-mut">On this page</h2>
<nav class="flex flex-col gap-0.5">
<a href="#tsl07-s1" aria-current="true" class="px-2.5 py-[7px] rounded-lg text-[13px] font-medium leading-snug no-underline text-tsl07-mut transition-colors duration-150 hover:text-tsl07-ink hover:bg-tsl07-hover aria-[current=true]:text-tsl07-accent aria-[current=true]:bg-tsl07-tint aria-[current=true]:font-semibold focus-visible:outline-2 focus-visible:outline-tsl07-accent focus-visible:outline-offset-1">Why grids beat floats</a>
<a href="#tsl07-s2" class="px-2.5 py-[7px] rounded-lg text-[13px] font-medium leading-snug no-underline text-tsl07-mut transition-colors duration-150 hover:text-tsl07-ink hover:bg-tsl07-hover focus-visible:outline-2 focus-visible:outline-tsl07-accent focus-visible:outline-offset-1">Track sizing deep-dive</a>
<a href="#tsl07-s3" class="px-2.5 py-[7px] rounded-lg text-[13px] font-medium leading-snug no-underline text-tsl07-mut transition-colors duration-150 hover:text-tsl07-ink hover:bg-tsl07-hover focus-visible:outline-2 focus-visible:outline-tsl07-accent focus-visible:outline-offset-1">Alignment properties</a>
<a href="#tsl07-s4" class="px-2.5 py-[7px] rounded-lg text-[13px] font-medium leading-snug no-underline text-tsl07-mut transition-colors duration-150 hover:text-tsl07-ink hover:bg-tsl07-hover focus-visible:outline-2 focus-visible:outline-tsl07-accent focus-visible:outline-offset-1">Subgrid in practice</a>
<a href="#tsl07-s5" class="px-2.5 py-[7px] rounded-lg text-[13px] font-medium leading-snug no-underline text-tsl07-mut transition-colors duration-150 hover:text-tsl07-ink hover:bg-tsl07-hover focus-visible:outline-2 focus-visible:outline-tsl07-accent focus-visible:outline-offset-1">Container queries</a>
</nav>
</aside>
<article>
<h1 id="tsl07-s1" class="scroll-mt-5 text-[26px] font-bold leading-tight tracking-tight mb-3.5">Modern CSS layout, properly</h1>
<p class="max-w-[62ch] text-[14.5px] leading-[1.75] text-tsl07-body text-pretty">Scroll this page — the rail sticks 24px from the top while the article flows past. The bar up top is a scroll-driven animation, no JS.</p>
<h2 id="tsl07-s2" class="scroll-mt-5 mt-[30px] mb-2.5 text-lg font-bold leading-snug">Track sizing deep-dive</h2>
<p class="max-w-[62ch] text-[14.5px] leading-[1.75] text-tsl07-body text-pretty">Fixed, fractional, minmax and fit-content tracks each answer a different layout question. Fractional units distribute leftover space, not total space — the distinction that explains most grid surprises.</p>
<h2 id="tsl07-s3" class="scroll-mt-5 mt-[30px] mb-2.5 text-lg font-bold leading-snug">Alignment properties</h2>
<p class="max-w-[62ch] text-[14.5px] leading-[1.75] text-tsl07-body text-pretty">justify- controls the inline axis, align- the block axis. The -content properties move tracks; -items move children within their cells; -self overrides per child.</p>
<h2 id="tsl07-s4" class="scroll-mt-5 mt-[30px] mb-2.5 text-lg font-bold leading-snug">Subgrid in practice</h2>
<p class="max-w-[62ch] text-[14.5px] leading-[1.75] text-tsl07-body text-pretty">Subgrid lets nested elements adopt parent rails — card grids with aligned footers, forms with aligned labels, sidebars with aligned badges (see demo 05).</p>
<h2 id="tsl07-s5" class="scroll-mt-5 mt-[30px] mb-2.5 text-lg font-bold leading-snug">Container queries</h2>
<p class="max-w-[62ch] text-[14.5px] leading-[1.75] text-tsl07-body text-pretty">Components respond to their container, not the viewport. Demo 29 rebuilds this sidebar pattern on container queries.</p>
</article>
</div>
</section>@import "tailwindcss";
@theme {
--color-tsl07-bg: #fcfcfd;
--color-tsl07-bg: oklch(0.99 0.002 250);
--color-tsl07-ink: #3b4049;
--color-tsl07-ink: oklch(0.27 0.015 250);
--color-tsl07-body: #565c66;
--color-tsl07-body: oklch(0.38 0.012 250);
--color-tsl07-mut: #767b85;
--color-tsl07-mut: oklch(0.52 0.012 250);
--color-tsl07-line: #e3e4e9;
--color-tsl07-line: oklch(0.9 0.007 250);
--color-tsl07-accent: #4360cb;
--color-tsl07-accent: oklch(0.55 0.16 250);
--color-tsl07-hover: rgb(59 64 73 / 0.05);
--color-tsl07-hover: color-mix(in oklab, oklch(0.27 0.015 250) 5%, transparent);
--color-tsl07-tint: rgb(67 96 203 / 0.09);
--color-tsl07-tint: color-mix(in oklab, oklch(0.55 0.16 250) 9%, transparent);
--font-tsl07-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
--animate-tsl07-grow: tsl07-grow linear;
}
@keyframes tsl07-grow {
from {
width: 0;
}
to {
width: 100%;
}
}
.tsl-07 {
width: 100%;
height: 100svh;
min-height: 100svh;
color-scheme: light;
}
/* No utility names a scroll timeline, and none should — this is plumbing,
not a design-scale value. The bar's animation itself IS a utility. */
.tsl-07 {
scroll-timeline: --tsl07-scroll block;
}
.tsl-07 > [aria-hidden="true"]:first-child {
animation-timeline: --tsl07-scroll;
}@import "tailwindcss";
@theme {
--color-tsl07-bg: #fcfcfd;
--color-tsl07-bg: oklch(0.99 0.002 250);
--color-tsl07-ink: #3b4049;
--color-tsl07-ink: oklch(0.27 0.015 250);
--color-tsl07-body: #565c66;
--color-tsl07-body: oklch(0.38 0.012 250);
--color-tsl07-mut: #767b85;
--color-tsl07-mut: oklch(0.52 0.012 250);
--color-tsl07-line: #e3e4e9;
--color-tsl07-line: oklch(0.9 0.007 250);
--color-tsl07-accent: #4360cb;
--color-tsl07-accent: oklch(0.55 0.16 250);
--color-tsl07-hover: rgb(59 64 73 / 0.05);
--color-tsl07-hover: color-mix(in oklab, oklch(0.27 0.015 250) 5%, transparent);
--color-tsl07-tint: rgb(67 96 203 / 0.09);
--color-tsl07-tint: color-mix(in oklab, oklch(0.55 0.16 250) 9%, transparent);
--font-tsl07-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
--animate-tsl07-grow: tsl07-grow linear;
}
@keyframes tsl07-grow {
from {
width: 0;
}
to {
width: 100%;
}
}
.tsl-07 {
width: 100%;
height: 100svh;
min-height: 100svh;
color-scheme: light;
}
/* No utility names a scroll timeline, and none should — this is plumbing,
not a design-scale value. The bar's animation itself IS a utility. */
.tsl-07 {
scroll-timeline: --tsl07-scroll block;
}
.tsl-07 > [aria-hidden="true"]:first-child {
animation-timeline: --tsl07-scroll;
}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-tsl07-bg: #fcfcfd;
--color-tsl07-bg: oklch(0.99 0.002 250);
--color-tsl07-ink: #3b4049;
--color-tsl07-ink: oklch(0.27 0.015 250);
--color-tsl07-body: #565c66;
--color-tsl07-body: oklch(0.38 0.012 250);
--color-tsl07-mut: #767b85;
--color-tsl07-mut: oklch(0.52 0.012 250);
--color-tsl07-line: #e3e4e9;
--color-tsl07-line: oklch(0.9 0.007 250);
--color-tsl07-accent: #4360cb;
--color-tsl07-accent: oklch(0.55 0.16 250);
--color-tsl07-hover: rgb(59 64 73 / 0.05);
--color-tsl07-hover: color-mix(in oklab, oklch(0.27 0.015 250) 5%, transparent);
--color-tsl07-tint: rgb(67 96 203 / 0.09);
--color-tsl07-tint: color-mix(in oklab, oklch(0.55 0.16 250) 9%, transparent);
--font-tsl07-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
--animate-tsl07-grow: tsl07-grow linear;
}
@keyframes tsl07-grow {
from {
width: 0;
}
to {
width: 100%;
}
}
.tsl-07 {
width: 100%;
height: 100svh;
min-height: 100svh;
color-scheme: light;
}
/* No utility names a scroll timeline, and none should — this is plumbing,
not a design-scale value. The bar's animation itself IS a utility. */
.tsl-07 {
scroll-timeline: --tsl07-scroll block;
}
.tsl-07 > [aria-hidden="true"]:first-child {
animation-timeline: --tsl07-scroll;
}@import "tailwindcss";
@theme {
--color-tsl07-bg: #fcfcfd;
--color-tsl07-bg: oklch(0.99 0.002 250);
--color-tsl07-ink: #3b4049;
--color-tsl07-ink: oklch(0.27 0.015 250);
--color-tsl07-body: #565c66;
--color-tsl07-body: oklch(0.38 0.012 250);
--color-tsl07-mut: #767b85;
--color-tsl07-mut: oklch(0.52 0.012 250);
--color-tsl07-line: #e3e4e9;
--color-tsl07-line: oklch(0.9 0.007 250);
--color-tsl07-accent: #4360cb;
--color-tsl07-accent: oklch(0.55 0.16 250);
--color-tsl07-hover: rgb(59 64 73 / 0.05);
--color-tsl07-hover: color-mix(in oklab, oklch(0.27 0.015 250) 5%, transparent);
--color-tsl07-tint: rgb(67 96 203 / 0.09);
--color-tsl07-tint: color-mix(in oklab, oklch(0.55 0.16 250) 9%, transparent);
--font-tsl07-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
--animate-tsl07-grow: tsl07-grow linear;
}
@keyframes tsl07-grow {
from {
width: 0;
}
to {
width: 100%;
}
}
.tsl-07 {
width: 100%;
height: 100svh;
min-height: 100svh;
color-scheme: light;
}
/* No utility names a scroll timeline, and none should — this is plumbing,
not a design-scale value. The bar's animation itself IS a utility. */
.tsl-07 {
scroll-timeline: --tsl07-scroll block;
}
.tsl-07 > [aria-hidden="true"]:first-child {
animation-timeline: --tsl07-scroll;
}<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.
- Sticky is the port's best advertisement:
sticky top-6 self-start max-h-[…] overflow-y-autoputs all three silent preconditions in one place, where a reviewer can see one is missing. --animate-tsl07-growin@thememints theanimate-*utility, butanimation-timelinehas to be attached in CSS. Splitting an animation across both layers is normal in v4 and worth doing: the markup still says what animates.- UI change from the CSS original: the article column is
minmax(0,1fr)so a long unbroken token cannot widen the grid, and the reduced-motion fallback shows a static full-width bar at 25% opacity rather than nothing at all.
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 Sticky Sidebar Layout with Scroll-Driven Progress Bar
Source: https://codefronts.com/layouts/tailwind-css-sidebar-layouts/tailwind-sticky-sidebar-scroll-driven-progress-bar/
The long-article companion rail: a TOC that rides along as you scroll, with the three constraints people always miss, plus a scroll-driven reading progress bar. The port is a good illustration of the split — sticky is three utilities, the scroll timeline stays CSS because it has no utility form and should not.
## HTML
```html
<section class="tsl-07 w-full h-svh min-h-svh overflow-y-auto font-tsl07-sans bg-tsl07-bg text-tsl07-ink">
<div aria-hidden="true" class="sticky top-0 z-10 h-[3px] w-0 bg-[linear-gradient(90deg,var(--color-tsl07-accent),oklch(0.65_0.15_300))] animate-tsl07-grow motion-reduce:animate-none motion-reduce:w-full motion-reduce:opacity-25"></div>
<div class="grid grid-cols-[230px_minmax(0,1fr)] gap-10 max-w-[920px] mx-auto px-6 pt-8 pb-16">
<aside aria-label="On this page" class="sticky top-6 self-start max-h-[calc(100svh-3rem)] overflow-y-auto border-r border-tsl07-line pr-5">
<h2 class="mb-3 text-[11px] font-semibold uppercase tracking-[0.09em] text-tsl07-mut">On this page</h2>
<nav class="flex flex-col gap-0.5">
<a href="#tsl07-s1" aria-current="true" class="px-2.5 py-[7px] rounded-lg text-[13px] font-medium leading-snug no-underline text-tsl07-mut transition-colors duration-150 hover:text-tsl07-ink hover:bg-tsl07-hover aria-[current=true]:text-tsl07-accent aria-[current=true]:bg-tsl07-tint aria-[current=true]:font-semibold focus-visible:outline-2 focus-visible:outline-tsl07-accent focus-visible:outline-offset-1">Why grids beat floats</a>
<a href="#tsl07-s2" class="px-2.5 py-[7px] rounded-lg text-[13px] font-medium leading-snug no-underline text-tsl07-mut transition-colors duration-150 hover:text-tsl07-ink hover:bg-tsl07-hover focus-visible:outline-2 focus-visible:outline-tsl07-accent focus-visible:outline-offset-1">Track sizing deep-dive</a>
<a href="#tsl07-s3" class="px-2.5 py-[7px] rounded-lg text-[13px] font-medium leading-snug no-underline text-tsl07-mut transition-colors duration-150 hover:text-tsl07-ink hover:bg-tsl07-hover focus-visible:outline-2 focus-visible:outline-tsl07-accent focus-visible:outline-offset-1">Alignment properties</a>
<a href="#tsl07-s4" class="px-2.5 py-[7px] rounded-lg text-[13px] font-medium leading-snug no-underline text-tsl07-mut transition-colors duration-150 hover:text-tsl07-ink hover:bg-tsl07-hover focus-visible:outline-2 focus-visible:outline-tsl07-accent focus-visible:outline-offset-1">Subgrid in practice</a>
<a href="#tsl07-s5" class="px-2.5 py-[7px] rounded-lg text-[13px] font-medium leading-snug no-underline text-tsl07-mut transition-colors duration-150 hover:text-tsl07-ink hover:bg-tsl07-hover focus-visible:outline-2 focus-visible:outline-tsl07-accent focus-visible:outline-offset-1">Container queries</a>
</nav>
</aside>
<article>
<h1 id="tsl07-s1" class="scroll-mt-5 text-[26px] font-bold leading-tight tracking-tight mb-3.5">Modern CSS layout, properly</h1>
<p class="max-w-[62ch] text-[14.5px] leading-[1.75] text-tsl07-body text-pretty">Scroll this page — the rail sticks 24px from the top while the article flows past. The bar up top is a scroll-driven animation, no JS.</p>
<h2 id="tsl07-s2" class="scroll-mt-5 mt-[30px] mb-2.5 text-lg font-bold leading-snug">Track sizing deep-dive</h2>
<p class="max-w-[62ch] text-[14.5px] leading-[1.75] text-tsl07-body text-pretty">Fixed, fractional, minmax and fit-content tracks each answer a different layout question. Fractional units distribute leftover space, not total space — the distinction that explains most grid surprises.</p>
<h2 id="tsl07-s3" class="scroll-mt-5 mt-[30px] mb-2.5 text-lg font-bold leading-snug">Alignment properties</h2>
<p class="max-w-[62ch] text-[14.5px] leading-[1.75] text-tsl07-body text-pretty">justify- controls the inline axis, align- the block axis. The -content properties move tracks; -items move children within their cells; -self overrides per child.</p>
<h2 id="tsl07-s4" class="scroll-mt-5 mt-[30px] mb-2.5 text-lg font-bold leading-snug">Subgrid in practice</h2>
<p class="max-w-[62ch] text-[14.5px] leading-[1.75] text-tsl07-body text-pretty">Subgrid lets nested elements adopt parent rails — card grids with aligned footers, forms with aligned labels, sidebars with aligned badges (see demo 05).</p>
<h2 id="tsl07-s5" class="scroll-mt-5 mt-[30px] mb-2.5 text-lg font-bold leading-snug">Container queries</h2>
<p class="max-w-[62ch] text-[14.5px] leading-[1.75] text-tsl07-body text-pretty">Components respond to their container, not the viewport. Demo 29 rebuilds this sidebar pattern on container queries.</p>
</article>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tsl07-bg: #fcfcfd;
--color-tsl07-bg: oklch(0.99 0.002 250);
--color-tsl07-ink: #3b4049;
--color-tsl07-ink: oklch(0.27 0.015 250);
--color-tsl07-body: #565c66;
--color-tsl07-body: oklch(0.38 0.012 250);
--color-tsl07-mut: #767b85;
--color-tsl07-mut: oklch(0.52 0.012 250);
--color-tsl07-line: #e3e4e9;
--color-tsl07-line: oklch(0.9 0.007 250);
--color-tsl07-accent: #4360cb;
--color-tsl07-accent: oklch(0.55 0.16 250);
--color-tsl07-hover: rgb(59 64 73 / 0.05);
--color-tsl07-hover: color-mix(in oklab, oklch(0.27 0.015 250) 5%, transparent);
--color-tsl07-tint: rgb(67 96 203 / 0.09);
--color-tsl07-tint: color-mix(in oklab, oklch(0.55 0.16 250) 9%, transparent);
--font-tsl07-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
--animate-tsl07-grow: tsl07-grow linear;
}
@keyframes tsl07-grow {
from {
width: 0;
}
to {
width: 100%;
}
}
.tsl-07 {
width: 100%;
height: 100svh;
min-height: 100svh;
color-scheme: light;
}
/* No utility names a scroll timeline, and none should — this is plumbing,
not a design-scale value. The bar's animation itself IS a utility. */
.tsl-07 {
scroll-timeline: --tsl07-scroll block;
}
.tsl-07 > [aria-hidden="true"]:first-child {
animation-timeline: --tsl07-scroll;
}
```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 Sticky Sidebar Layout with Scroll-Driven Progress Bar
Source: https://codefronts.com/layouts/tailwind-css-sidebar-layouts/tailwind-sticky-sidebar-scroll-driven-progress-bar/
The long-article companion rail: a TOC that rides along as you scroll, with the three constraints people always miss, plus a scroll-driven reading progress bar. The port is a good illustration of the split — sticky is three utilities, the scroll timeline stays CSS because it has no utility form and should not.
## HTML
```html
<section class="tsl-07 w-full h-svh min-h-svh overflow-y-auto font-tsl07-sans bg-tsl07-bg text-tsl07-ink">
<div aria-hidden="true" class="sticky top-0 z-10 h-[3px] w-0 bg-[linear-gradient(90deg,var(--color-tsl07-accent),oklch(0.65_0.15_300))] animate-tsl07-grow motion-reduce:animate-none motion-reduce:w-full motion-reduce:opacity-25"></div>
<div class="grid grid-cols-[230px_minmax(0,1fr)] gap-10 max-w-[920px] mx-auto px-6 pt-8 pb-16">
<aside aria-label="On this page" class="sticky top-6 self-start max-h-[calc(100svh-3rem)] overflow-y-auto border-r border-tsl07-line pr-5">
<h2 class="mb-3 text-[11px] font-semibold uppercase tracking-[0.09em] text-tsl07-mut">On this page</h2>
<nav class="flex flex-col gap-0.5">
<a href="#tsl07-s1" aria-current="true" class="px-2.5 py-[7px] rounded-lg text-[13px] font-medium leading-snug no-underline text-tsl07-mut transition-colors duration-150 hover:text-tsl07-ink hover:bg-tsl07-hover aria-[current=true]:text-tsl07-accent aria-[current=true]:bg-tsl07-tint aria-[current=true]:font-semibold focus-visible:outline-2 focus-visible:outline-tsl07-accent focus-visible:outline-offset-1">Why grids beat floats</a>
<a href="#tsl07-s2" class="px-2.5 py-[7px] rounded-lg text-[13px] font-medium leading-snug no-underline text-tsl07-mut transition-colors duration-150 hover:text-tsl07-ink hover:bg-tsl07-hover focus-visible:outline-2 focus-visible:outline-tsl07-accent focus-visible:outline-offset-1">Track sizing deep-dive</a>
<a href="#tsl07-s3" class="px-2.5 py-[7px] rounded-lg text-[13px] font-medium leading-snug no-underline text-tsl07-mut transition-colors duration-150 hover:text-tsl07-ink hover:bg-tsl07-hover focus-visible:outline-2 focus-visible:outline-tsl07-accent focus-visible:outline-offset-1">Alignment properties</a>
<a href="#tsl07-s4" class="px-2.5 py-[7px] rounded-lg text-[13px] font-medium leading-snug no-underline text-tsl07-mut transition-colors duration-150 hover:text-tsl07-ink hover:bg-tsl07-hover focus-visible:outline-2 focus-visible:outline-tsl07-accent focus-visible:outline-offset-1">Subgrid in practice</a>
<a href="#tsl07-s5" class="px-2.5 py-[7px] rounded-lg text-[13px] font-medium leading-snug no-underline text-tsl07-mut transition-colors duration-150 hover:text-tsl07-ink hover:bg-tsl07-hover focus-visible:outline-2 focus-visible:outline-tsl07-accent focus-visible:outline-offset-1">Container queries</a>
</nav>
</aside>
<article>
<h1 id="tsl07-s1" class="scroll-mt-5 text-[26px] font-bold leading-tight tracking-tight mb-3.5">Modern CSS layout, properly</h1>
<p class="max-w-[62ch] text-[14.5px] leading-[1.75] text-tsl07-body text-pretty">Scroll this page — the rail sticks 24px from the top while the article flows past. The bar up top is a scroll-driven animation, no JS.</p>
<h2 id="tsl07-s2" class="scroll-mt-5 mt-[30px] mb-2.5 text-lg font-bold leading-snug">Track sizing deep-dive</h2>
<p class="max-w-[62ch] text-[14.5px] leading-[1.75] text-tsl07-body text-pretty">Fixed, fractional, minmax and fit-content tracks each answer a different layout question. Fractional units distribute leftover space, not total space — the distinction that explains most grid surprises.</p>
<h2 id="tsl07-s3" class="scroll-mt-5 mt-[30px] mb-2.5 text-lg font-bold leading-snug">Alignment properties</h2>
<p class="max-w-[62ch] text-[14.5px] leading-[1.75] text-tsl07-body text-pretty">justify- controls the inline axis, align- the block axis. The -content properties move tracks; -items move children within their cells; -self overrides per child.</p>
<h2 id="tsl07-s4" class="scroll-mt-5 mt-[30px] mb-2.5 text-lg font-bold leading-snug">Subgrid in practice</h2>
<p class="max-w-[62ch] text-[14.5px] leading-[1.75] text-tsl07-body text-pretty">Subgrid lets nested elements adopt parent rails — card grids with aligned footers, forms with aligned labels, sidebars with aligned badges (see demo 05).</p>
<h2 id="tsl07-s5" class="scroll-mt-5 mt-[30px] mb-2.5 text-lg font-bold leading-snug">Container queries</h2>
<p class="max-w-[62ch] text-[14.5px] leading-[1.75] text-tsl07-body text-pretty">Components respond to their container, not the viewport. Demo 29 rebuilds this sidebar pattern on container queries.</p>
</article>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tsl07-bg: #fcfcfd;
--color-tsl07-bg: oklch(0.99 0.002 250);
--color-tsl07-ink: #3b4049;
--color-tsl07-ink: oklch(0.27 0.015 250);
--color-tsl07-body: #565c66;
--color-tsl07-body: oklch(0.38 0.012 250);
--color-tsl07-mut: #767b85;
--color-tsl07-mut: oklch(0.52 0.012 250);
--color-tsl07-line: #e3e4e9;
--color-tsl07-line: oklch(0.9 0.007 250);
--color-tsl07-accent: #4360cb;
--color-tsl07-accent: oklch(0.55 0.16 250);
--color-tsl07-hover: rgb(59 64 73 / 0.05);
--color-tsl07-hover: color-mix(in oklab, oklch(0.27 0.015 250) 5%, transparent);
--color-tsl07-tint: rgb(67 96 203 / 0.09);
--color-tsl07-tint: color-mix(in oklab, oklch(0.55 0.16 250) 9%, transparent);
--font-tsl07-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
--animate-tsl07-grow: tsl07-grow linear;
}
@keyframes tsl07-grow {
from {
width: 0;
}
to {
width: 100%;
}
}
.tsl-07 {
width: 100%;
height: 100svh;
min-height: 100svh;
color-scheme: light;
}
/* No utility names a scroll timeline, and none should — this is plumbing,
not a design-scale value. The bar's animation itself IS a utility. */
.tsl-07 {
scroll-timeline: --tsl07-scroll block;
}
.tsl-07 > [aria-hidden="true"]:first-child {
animation-timeline: --tsl07-scroll;
}
```How this works
Sticky fails silently unless three things hold, and the utility form makes all three visible on one element: an explicit inset (top-6), self-start (grid items stretch by default, and a stretched rail has no room to travel), and a height cap with internal scrolling. Reading them in a single class list is the strongest argument for the port — in the CSS original the three declarations were three lines that a refactor could separate.
The progress bar cannot be a utility, and should not be. scroll-timeline: --tsl07-scroll block names the scroller's axis as a timeline; the bar's keyframes run on it. Naming a timeline is not a design-scale value. What v4 does contribute is --animate-tsl07-grow in @theme, which mints animate-tsl07-grow so the markup still declares the animation.
Make it yours
- Match the top offset to your real header:
top-[calc(var(--header-h)+1rem)]. - Compose with demo 03's
:targethighlighting for a scroll-synced TOC. - Blogs conventionally put the TOC on the right — swap to
grid-cols-[minmax(0,1fr)_230px]andorder-*.
Gotchas — read before shipping
- Any ancestor with
overflow-hiddenbetween the sticky element and its scroller kills sticking — the most-Googled sticky bug, andoverflow-hiddenis an easy utility to add absent-mindedly. self-start(or an explicit height) is mandatory in grid and flex parents.- The scroll timeline must be declared on the element that actually scrolls — here the section, not the window.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 115+ | 26+ | — | 115+ |
position:sticky is universal since 2017; the progress bar is progressive enhancement (Firefox behind a flag) and vanishes cleanly where unsupported.