12 / 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 Two-Pane Webmail Layout with List and Reading Pane
The list/detail split every mail client, messenger and support inbox uses: folder rail, 320px message list, reading pane — three independent scroll regions in one grid. The port's pleasure is line-clamp-2, which replaces the four-declaration -webkit-box incantation with one utility.
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-12 w-full h-svh min-h-svh grid grid-cols-[200px_320px_1fr] font-tsl12-sans bg-white text-tsl12-ink">
<aside aria-label="Folders" class="bg-tsl12-panel border-r border-tsl12-line px-2.5 py-4 overflow-y-auto">
<button type="button" class="w-full min-h-11 mb-3.5 rounded-[10px] border-0 bg-tsl12-accent text-white text-[13.5px] font-semibold cursor-pointer transition-[filter] duration-200 hover:brightness-110 focus-visible:outline-2 focus-visible:outline-tsl12-accent focus-visible:outline-offset-2">Compose</button>
<nav class="flex flex-col gap-0.5">
<a href="#" aria-current="page" class="flex items-center justify-between min-h-11 px-3 rounded-[9px] text-[13.5px] font-medium text-tsl12-mut no-underline transition-colors duration-150 hover:text-tsl12-ink hover:bg-tsl12-hover aria-[current=page]:text-tsl12-ink aria-[current=page]:bg-tsl12-tint aria-[current=page]:font-semibold focus-visible:outline-2 focus-visible:outline-tsl12-accent focus-visible:outline-offset-1">Inbox <span class="text-[11.5px] font-semibold text-tsl12-accent">12</span></a>
<a href="#" class="flex items-center justify-between min-h-11 px-3 rounded-[9px] text-[13.5px] font-medium text-tsl12-mut no-underline transition-colors duration-150 hover:text-tsl12-ink hover:bg-tsl12-hover focus-visible:outline-2 focus-visible:outline-tsl12-accent focus-visible:outline-offset-1">Starred</a>
<a href="#" class="flex items-center justify-between min-h-11 px-3 rounded-[9px] text-[13.5px] font-medium text-tsl12-mut no-underline transition-colors duration-150 hover:text-tsl12-ink hover:bg-tsl12-hover focus-visible:outline-2 focus-visible:outline-tsl12-accent focus-visible:outline-offset-1">Sent</a>
<a href="#" class="flex items-center justify-between min-h-11 px-3 rounded-[9px] text-[13.5px] font-medium text-tsl12-mut no-underline transition-colors duration-150 hover:text-tsl12-ink hover:bg-tsl12-hover focus-visible:outline-2 focus-visible:outline-tsl12-accent focus-visible:outline-offset-1">Drafts <span class="text-[11.5px] font-semibold text-tsl12-accent">2</span></a>
<a href="#" class="flex items-center justify-between min-h-11 px-3 rounded-[9px] text-[13.5px] font-medium text-tsl12-mut no-underline transition-colors duration-150 hover:text-tsl12-ink hover:bg-tsl12-hover focus-visible:outline-2 focus-visible:outline-tsl12-accent focus-visible:outline-offset-1">Archive</a>
</nav>
</aside>
<div role="listbox" aria-label="Messages" class="border-r border-tsl12-line overflow-y-auto overscroll-contain [scrollbar-gutter:stable]">
<a href="#" role="option" aria-selected="false" data-unread class="group grid grid-cols-[auto_1fr_auto] items-start gap-2.5 px-3.5 py-3 border-b border-tsl12-line no-underline text-inherit transition-colors duration-150 hover:bg-tsl12-rowhover aria-selected:bg-tsl12-tint aria-selected:shadow-[inset_2.5px_0_0_var(--color-tsl12-accent)] focus-visible:outline-2 focus-visible:outline-tsl12-accent focus-visible:-outline-offset-2"><span aria-hidden="true" class="w-[7px] h-[7px] mt-[7px] rounded-full bg-transparent group-data-unread:bg-tsl12-accent"></span><span class="min-w-0 grid gap-[3px]"><strong class="text-[13.5px] font-medium leading-tight group-data-unread:font-bold">Priya Raman</strong><em class="not-italic text-[12.5px] leading-[1.45] text-tsl12-mut line-clamp-2">Q3 roadmap review — the container query rollout is ahead of schedule and I think we can…</em></span><time class="mt-0.5 text-[11.5px] font-medium text-tsl12-mut">09:41</time></a>
<a href="#" role="option" aria-selected="true" class="group grid grid-cols-[auto_1fr_auto] items-start gap-2.5 px-3.5 py-3 border-b border-tsl12-line no-underline text-inherit transition-colors duration-150 hover:bg-tsl12-rowhover aria-selected:bg-tsl12-tint aria-selected:shadow-[inset_2.5px_0_0_var(--color-tsl12-accent)] focus-visible:outline-2 focus-visible:outline-tsl12-accent focus-visible:-outline-offset-2"><span aria-hidden="true" class="w-[7px] h-[7px] mt-[7px] rounded-full bg-transparent group-data-unread:bg-tsl12-accent"></span><span class="min-w-0 grid gap-[3px]"><strong class="text-[13.5px] font-medium leading-tight group-data-unread:font-bold">Marcus Webb</strong><em class="not-italic text-[12.5px] leading-[1.45] text-tsl12-mut line-clamp-2">Re: Design tokens sync — agreed, let's move the semantic layer into the base package…</em></span><time class="mt-0.5 text-[11.5px] font-medium text-tsl12-mut">08:17</time></a>
<a href="#" role="option" aria-selected="false" data-unread class="group grid grid-cols-[auto_1fr_auto] items-start gap-2.5 px-3.5 py-3 border-b border-tsl12-line no-underline text-inherit transition-colors duration-150 hover:bg-tsl12-rowhover aria-selected:bg-tsl12-tint aria-selected:shadow-[inset_2.5px_0_0_var(--color-tsl12-accent)] focus-visible:outline-2 focus-visible:outline-tsl12-accent focus-visible:-outline-offset-2"><span aria-hidden="true" class="w-[7px] h-[7px] mt-[7px] rounded-full bg-transparent group-data-unread:bg-tsl12-accent"></span><span class="min-w-0 grid gap-[3px]"><strong class="text-[13.5px] font-medium leading-tight group-data-unread:font-bold">Linear</strong><em class="not-italic text-[12.5px] leading-[1.45] text-tsl12-mut line-clamp-2">Weekly digest: 14 issues closed, 3 in review. Cycle 42 completes Friday.</em></span><time class="mt-0.5 text-[11.5px] font-medium text-tsl12-mut">Mon</time></a>
<a href="#" role="option" aria-selected="false" class="group grid grid-cols-[auto_1fr_auto] items-start gap-2.5 px-3.5 py-3 border-b border-tsl12-line no-underline text-inherit transition-colors duration-150 hover:bg-tsl12-rowhover aria-selected:bg-tsl12-tint aria-selected:shadow-[inset_2.5px_0_0_var(--color-tsl12-accent)] focus-visible:outline-2 focus-visible:outline-tsl12-accent focus-visible:-outline-offset-2"><span aria-hidden="true" class="w-[7px] h-[7px] mt-[7px] rounded-full bg-transparent group-data-unread:bg-tsl12-accent"></span><span class="min-w-0 grid gap-[3px]"><strong class="text-[13.5px] font-medium leading-tight group-data-unread:font-bold">Amara Osei</strong><em class="not-italic text-[12.5px] leading-[1.45] text-tsl12-mut line-clamp-2">Accessibility audit results — great news on the focus-visible coverage, two items left…</em></span><time class="mt-0.5 text-[11.5px] font-medium text-tsl12-mut">Mon</time></a>
</div>
<main class="overflow-y-auto px-[38px] py-8 [scrollbar-gutter:stable]">
<h1 class="text-xl font-bold leading-snug">Re: Design tokens sync</h1>
<p class="mt-2.5 mb-[22px] text-[12.5px] font-medium text-tsl12-mut">Marcus Webb · to Design Platform · 08:17</p>
<p class="max-w-[60ch] mb-3.5 text-sm leading-[1.7] text-tsl12-body text-pretty">Agreed — let's move the semantic layer into the base package. The component tokens can then alias into it, which keeps theming to a single override point.</p>
<p class="max-w-[60ch] text-sm leading-[1.7] text-tsl12-body text-pretty">I'll draft the migration doc today. The mail list on your left scrolls independently of this pane — three scroll regions, one grid.</p>
</main>
</section><section class="tsl-12 w-full h-svh min-h-svh grid grid-cols-[200px_320px_1fr] font-tsl12-sans bg-white text-tsl12-ink">
<aside aria-label="Folders" class="bg-tsl12-panel border-r border-tsl12-line px-2.5 py-4 overflow-y-auto">
<button type="button" class="w-full min-h-11 mb-3.5 rounded-[10px] border-0 bg-tsl12-accent text-white text-[13.5px] font-semibold cursor-pointer transition-[filter] duration-200 hover:brightness-110 focus-visible:outline-2 focus-visible:outline-tsl12-accent focus-visible:outline-offset-2">Compose</button>
<nav class="flex flex-col gap-0.5">
<a href="#" aria-current="page" class="flex items-center justify-between min-h-11 px-3 rounded-[9px] text-[13.5px] font-medium text-tsl12-mut no-underline transition-colors duration-150 hover:text-tsl12-ink hover:bg-tsl12-hover aria-[current=page]:text-tsl12-ink aria-[current=page]:bg-tsl12-tint aria-[current=page]:font-semibold focus-visible:outline-2 focus-visible:outline-tsl12-accent focus-visible:outline-offset-1">Inbox <span class="text-[11.5px] font-semibold text-tsl12-accent">12</span></a>
<a href="#" class="flex items-center justify-between min-h-11 px-3 rounded-[9px] text-[13.5px] font-medium text-tsl12-mut no-underline transition-colors duration-150 hover:text-tsl12-ink hover:bg-tsl12-hover focus-visible:outline-2 focus-visible:outline-tsl12-accent focus-visible:outline-offset-1">Starred</a>
<a href="#" class="flex items-center justify-between min-h-11 px-3 rounded-[9px] text-[13.5px] font-medium text-tsl12-mut no-underline transition-colors duration-150 hover:text-tsl12-ink hover:bg-tsl12-hover focus-visible:outline-2 focus-visible:outline-tsl12-accent focus-visible:outline-offset-1">Sent</a>
<a href="#" class="flex items-center justify-between min-h-11 px-3 rounded-[9px] text-[13.5px] font-medium text-tsl12-mut no-underline transition-colors duration-150 hover:text-tsl12-ink hover:bg-tsl12-hover focus-visible:outline-2 focus-visible:outline-tsl12-accent focus-visible:outline-offset-1">Drafts <span class="text-[11.5px] font-semibold text-tsl12-accent">2</span></a>
<a href="#" class="flex items-center justify-between min-h-11 px-3 rounded-[9px] text-[13.5px] font-medium text-tsl12-mut no-underline transition-colors duration-150 hover:text-tsl12-ink hover:bg-tsl12-hover focus-visible:outline-2 focus-visible:outline-tsl12-accent focus-visible:outline-offset-1">Archive</a>
</nav>
</aside>
<div role="listbox" aria-label="Messages" class="border-r border-tsl12-line overflow-y-auto overscroll-contain [scrollbar-gutter:stable]">
<a href="#" role="option" aria-selected="false" data-unread class="group grid grid-cols-[auto_1fr_auto] items-start gap-2.5 px-3.5 py-3 border-b border-tsl12-line no-underline text-inherit transition-colors duration-150 hover:bg-tsl12-rowhover aria-selected:bg-tsl12-tint aria-selected:shadow-[inset_2.5px_0_0_var(--color-tsl12-accent)] focus-visible:outline-2 focus-visible:outline-tsl12-accent focus-visible:-outline-offset-2"><span aria-hidden="true" class="w-[7px] h-[7px] mt-[7px] rounded-full bg-transparent group-data-unread:bg-tsl12-accent"></span><span class="min-w-0 grid gap-[3px]"><strong class="text-[13.5px] font-medium leading-tight group-data-unread:font-bold">Priya Raman</strong><em class="not-italic text-[12.5px] leading-[1.45] text-tsl12-mut line-clamp-2">Q3 roadmap review — the container query rollout is ahead of schedule and I think we can…</em></span><time class="mt-0.5 text-[11.5px] font-medium text-tsl12-mut">09:41</time></a>
<a href="#" role="option" aria-selected="true" class="group grid grid-cols-[auto_1fr_auto] items-start gap-2.5 px-3.5 py-3 border-b border-tsl12-line no-underline text-inherit transition-colors duration-150 hover:bg-tsl12-rowhover aria-selected:bg-tsl12-tint aria-selected:shadow-[inset_2.5px_0_0_var(--color-tsl12-accent)] focus-visible:outline-2 focus-visible:outline-tsl12-accent focus-visible:-outline-offset-2"><span aria-hidden="true" class="w-[7px] h-[7px] mt-[7px] rounded-full bg-transparent group-data-unread:bg-tsl12-accent"></span><span class="min-w-0 grid gap-[3px]"><strong class="text-[13.5px] font-medium leading-tight group-data-unread:font-bold">Marcus Webb</strong><em class="not-italic text-[12.5px] leading-[1.45] text-tsl12-mut line-clamp-2">Re: Design tokens sync — agreed, let's move the semantic layer into the base package…</em></span><time class="mt-0.5 text-[11.5px] font-medium text-tsl12-mut">08:17</time></a>
<a href="#" role="option" aria-selected="false" data-unread class="group grid grid-cols-[auto_1fr_auto] items-start gap-2.5 px-3.5 py-3 border-b border-tsl12-line no-underline text-inherit transition-colors duration-150 hover:bg-tsl12-rowhover aria-selected:bg-tsl12-tint aria-selected:shadow-[inset_2.5px_0_0_var(--color-tsl12-accent)] focus-visible:outline-2 focus-visible:outline-tsl12-accent focus-visible:-outline-offset-2"><span aria-hidden="true" class="w-[7px] h-[7px] mt-[7px] rounded-full bg-transparent group-data-unread:bg-tsl12-accent"></span><span class="min-w-0 grid gap-[3px]"><strong class="text-[13.5px] font-medium leading-tight group-data-unread:font-bold">Linear</strong><em class="not-italic text-[12.5px] leading-[1.45] text-tsl12-mut line-clamp-2">Weekly digest: 14 issues closed, 3 in review. Cycle 42 completes Friday.</em></span><time class="mt-0.5 text-[11.5px] font-medium text-tsl12-mut">Mon</time></a>
<a href="#" role="option" aria-selected="false" class="group grid grid-cols-[auto_1fr_auto] items-start gap-2.5 px-3.5 py-3 border-b border-tsl12-line no-underline text-inherit transition-colors duration-150 hover:bg-tsl12-rowhover aria-selected:bg-tsl12-tint aria-selected:shadow-[inset_2.5px_0_0_var(--color-tsl12-accent)] focus-visible:outline-2 focus-visible:outline-tsl12-accent focus-visible:-outline-offset-2"><span aria-hidden="true" class="w-[7px] h-[7px] mt-[7px] rounded-full bg-transparent group-data-unread:bg-tsl12-accent"></span><span class="min-w-0 grid gap-[3px]"><strong class="text-[13.5px] font-medium leading-tight group-data-unread:font-bold">Amara Osei</strong><em class="not-italic text-[12.5px] leading-[1.45] text-tsl12-mut line-clamp-2">Accessibility audit results — great news on the focus-visible coverage, two items left…</em></span><time class="mt-0.5 text-[11.5px] font-medium text-tsl12-mut">Mon</time></a>
</div>
<main class="overflow-y-auto px-[38px] py-8 [scrollbar-gutter:stable]">
<h1 class="text-xl font-bold leading-snug">Re: Design tokens sync</h1>
<p class="mt-2.5 mb-[22px] text-[12.5px] font-medium text-tsl12-mut">Marcus Webb · to Design Platform · 08:17</p>
<p class="max-w-[60ch] mb-3.5 text-sm leading-[1.7] text-tsl12-body text-pretty">Agreed — let's move the semantic layer into the base package. The component tokens can then alias into it, which keeps theming to a single override point.</p>
<p class="max-w-[60ch] text-sm leading-[1.7] text-tsl12-body text-pretty">I'll draft the migration doc today. The mail list on your left scrolls independently of this pane — three scroll regions, one grid.</p>
</main>
</section>@import "tailwindcss";
@theme {
--color-tsl12-panel: #f3f4f6;
--color-tsl12-panel: oklch(0.965 0.005 240);
--color-tsl12-ink: #363b43;
--color-tsl12-ink: oklch(0.25 0.02 240);
--color-tsl12-body: #4c525c;
--color-tsl12-body: oklch(0.35 0.015 240);
--color-tsl12-mut: #71767f;
--color-tsl12-mut: oklch(0.5 0.015 240);
--color-tsl12-line: #dfe1e5;
--color-tsl12-line: oklch(0.89 0.007 240);
--color-tsl12-accent: #3f5fd0;
--color-tsl12-accent: oklch(0.52 0.17 255);
--color-tsl12-hover: rgb(54 59 67 / 0.05);
--color-tsl12-hover: color-mix(in oklab, oklch(0.25 0.02 240) 5%, transparent);
--color-tsl12-rowhover: rgb(54 59 67 / 0.035);
--color-tsl12-rowhover: color-mix(in oklab, oklch(0.25 0.02 240) 3.5%, transparent);
--color-tsl12-tint: rgb(63 95 208 / 0.1);
--color-tsl12-tint: color-mix(in oklab, oklch(0.52 0.17 255) 10%, transparent);
--font-tsl12-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}
.tsl-12 {
width: 100%;
height: 100svh;
min-height: 100svh;
color-scheme: light;
}@import "tailwindcss";
@theme {
--color-tsl12-panel: #f3f4f6;
--color-tsl12-panel: oklch(0.965 0.005 240);
--color-tsl12-ink: #363b43;
--color-tsl12-ink: oklch(0.25 0.02 240);
--color-tsl12-body: #4c525c;
--color-tsl12-body: oklch(0.35 0.015 240);
--color-tsl12-mut: #71767f;
--color-tsl12-mut: oklch(0.5 0.015 240);
--color-tsl12-line: #dfe1e5;
--color-tsl12-line: oklch(0.89 0.007 240);
--color-tsl12-accent: #3f5fd0;
--color-tsl12-accent: oklch(0.52 0.17 255);
--color-tsl12-hover: rgb(54 59 67 / 0.05);
--color-tsl12-hover: color-mix(in oklab, oklch(0.25 0.02 240) 5%, transparent);
--color-tsl12-rowhover: rgb(54 59 67 / 0.035);
--color-tsl12-rowhover: color-mix(in oklab, oklch(0.25 0.02 240) 3.5%, transparent);
--color-tsl12-tint: rgb(63 95 208 / 0.1);
--color-tsl12-tint: color-mix(in oklab, oklch(0.52 0.17 255) 10%, transparent);
--font-tsl12-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}
.tsl-12 {
width: 100%;
height: 100svh;
min-height: 100svh;
color-scheme: light;
}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-tsl12-panel: #f3f4f6;
--color-tsl12-panel: oklch(0.965 0.005 240);
--color-tsl12-ink: #363b43;
--color-tsl12-ink: oklch(0.25 0.02 240);
--color-tsl12-body: #4c525c;
--color-tsl12-body: oklch(0.35 0.015 240);
--color-tsl12-mut: #71767f;
--color-tsl12-mut: oklch(0.5 0.015 240);
--color-tsl12-line: #dfe1e5;
--color-tsl12-line: oklch(0.89 0.007 240);
--color-tsl12-accent: #3f5fd0;
--color-tsl12-accent: oklch(0.52 0.17 255);
--color-tsl12-hover: rgb(54 59 67 / 0.05);
--color-tsl12-hover: color-mix(in oklab, oklch(0.25 0.02 240) 5%, transparent);
--color-tsl12-rowhover: rgb(54 59 67 / 0.035);
--color-tsl12-rowhover: color-mix(in oklab, oklch(0.25 0.02 240) 3.5%, transparent);
--color-tsl12-tint: rgb(63 95 208 / 0.1);
--color-tsl12-tint: color-mix(in oklab, oklch(0.52 0.17 255) 10%, transparent);
--font-tsl12-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}
.tsl-12 {
width: 100%;
height: 100svh;
min-height: 100svh;
color-scheme: light;
}@import "tailwindcss";
@theme {
--color-tsl12-panel: #f3f4f6;
--color-tsl12-panel: oklch(0.965 0.005 240);
--color-tsl12-ink: #363b43;
--color-tsl12-ink: oklch(0.25 0.02 240);
--color-tsl12-body: #4c525c;
--color-tsl12-body: oklch(0.35 0.015 240);
--color-tsl12-mut: #71767f;
--color-tsl12-mut: oklch(0.5 0.015 240);
--color-tsl12-line: #dfe1e5;
--color-tsl12-line: oklch(0.89 0.007 240);
--color-tsl12-accent: #3f5fd0;
--color-tsl12-accent: oklch(0.52 0.17 255);
--color-tsl12-hover: rgb(54 59 67 / 0.05);
--color-tsl12-hover: color-mix(in oklab, oklch(0.25 0.02 240) 5%, transparent);
--color-tsl12-rowhover: rgb(54 59 67 / 0.035);
--color-tsl12-rowhover: color-mix(in oklab, oklch(0.25 0.02 240) 3.5%, transparent);
--color-tsl12-tint: rgb(63 95 208 / 0.1);
--color-tsl12-tint: color-mix(in oklab, oklch(0.52 0.17 255) 10%, transparent);
--font-tsl12-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}
.tsl-12 {
width: 100%;
height: 100svh;
min-height: 100svh;
color-scheme: light;
}<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.
group-data-unread:is how a row-level attribute reaches the dot and the sender name without a descendant selector. The row declaresgroup, the children opt in — the same shape as the CSS original, minus the coupling to a class name.line-clamp-2deserves its reputation. Four declarations, one of them prefixed, all of them required, collapse into a single utility that cannot be half-applied.- UI change from the CSS original: folder rows and the compose button are
min-h-11, the list gainedscrollbar-gutter:stableso hover-revealed scrollbars stop nudging rows, and the reading pane's body text moved to a dedicated token at 4.6:1 contrast rather than an inlineoklch(0.35…).
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 Two-Pane Webmail Layout with List and Reading Pane
Source: https://codefronts.com/layouts/tailwind-css-sidebar-layouts/tailwind-two-pane-webmail-list-reading-layout/
The list/detail split every mail client, messenger and support inbox uses: folder rail, 320px message list, reading pane — three independent scroll regions in one grid. The port's pleasure is line-clamp-2, which replaces the four-declaration -webkit-box incantation with one utility.
## HTML
```html
<section class="tsl-12 w-full h-svh min-h-svh grid grid-cols-[200px_320px_1fr] font-tsl12-sans bg-white text-tsl12-ink">
<aside aria-label="Folders" class="bg-tsl12-panel border-r border-tsl12-line px-2.5 py-4 overflow-y-auto">
<button type="button" class="w-full min-h-11 mb-3.5 rounded-[10px] border-0 bg-tsl12-accent text-white text-[13.5px] font-semibold cursor-pointer transition-[filter] duration-200 hover:brightness-110 focus-visible:outline-2 focus-visible:outline-tsl12-accent focus-visible:outline-offset-2">Compose</button>
<nav class="flex flex-col gap-0.5">
<a href="#" aria-current="page" class="flex items-center justify-between min-h-11 px-3 rounded-[9px] text-[13.5px] font-medium text-tsl12-mut no-underline transition-colors duration-150 hover:text-tsl12-ink hover:bg-tsl12-hover aria-[current=page]:text-tsl12-ink aria-[current=page]:bg-tsl12-tint aria-[current=page]:font-semibold focus-visible:outline-2 focus-visible:outline-tsl12-accent focus-visible:outline-offset-1">Inbox <span class="text-[11.5px] font-semibold text-tsl12-accent">12</span></a>
<a href="#" class="flex items-center justify-between min-h-11 px-3 rounded-[9px] text-[13.5px] font-medium text-tsl12-mut no-underline transition-colors duration-150 hover:text-tsl12-ink hover:bg-tsl12-hover focus-visible:outline-2 focus-visible:outline-tsl12-accent focus-visible:outline-offset-1">Starred</a>
<a href="#" class="flex items-center justify-between min-h-11 px-3 rounded-[9px] text-[13.5px] font-medium text-tsl12-mut no-underline transition-colors duration-150 hover:text-tsl12-ink hover:bg-tsl12-hover focus-visible:outline-2 focus-visible:outline-tsl12-accent focus-visible:outline-offset-1">Sent</a>
<a href="#" class="flex items-center justify-between min-h-11 px-3 rounded-[9px] text-[13.5px] font-medium text-tsl12-mut no-underline transition-colors duration-150 hover:text-tsl12-ink hover:bg-tsl12-hover focus-visible:outline-2 focus-visible:outline-tsl12-accent focus-visible:outline-offset-1">Drafts <span class="text-[11.5px] font-semibold text-tsl12-accent">2</span></a>
<a href="#" class="flex items-center justify-between min-h-11 px-3 rounded-[9px] text-[13.5px] font-medium text-tsl12-mut no-underline transition-colors duration-150 hover:text-tsl12-ink hover:bg-tsl12-hover focus-visible:outline-2 focus-visible:outline-tsl12-accent focus-visible:outline-offset-1">Archive</a>
</nav>
</aside>
<div role="listbox" aria-label="Messages" class="border-r border-tsl12-line overflow-y-auto overscroll-contain [scrollbar-gutter:stable]">
<a href="#" role="option" aria-selected="false" data-unread class="group grid grid-cols-[auto_1fr_auto] items-start gap-2.5 px-3.5 py-3 border-b border-tsl12-line no-underline text-inherit transition-colors duration-150 hover:bg-tsl12-rowhover aria-selected:bg-tsl12-tint aria-selected:shadow-[inset_2.5px_0_0_var(--color-tsl12-accent)] focus-visible:outline-2 focus-visible:outline-tsl12-accent focus-visible:-outline-offset-2"><span aria-hidden="true" class="w-[7px] h-[7px] mt-[7px] rounded-full bg-transparent group-data-unread:bg-tsl12-accent"></span><span class="min-w-0 grid gap-[3px]"><strong class="text-[13.5px] font-medium leading-tight group-data-unread:font-bold">Priya Raman</strong><em class="not-italic text-[12.5px] leading-[1.45] text-tsl12-mut line-clamp-2">Q3 roadmap review — the container query rollout is ahead of schedule and I think we can…</em></span><time class="mt-0.5 text-[11.5px] font-medium text-tsl12-mut">09:41</time></a>
<a href="#" role="option" aria-selected="true" class="group grid grid-cols-[auto_1fr_auto] items-start gap-2.5 px-3.5 py-3 border-b border-tsl12-line no-underline text-inherit transition-colors duration-150 hover:bg-tsl12-rowhover aria-selected:bg-tsl12-tint aria-selected:shadow-[inset_2.5px_0_0_var(--color-tsl12-accent)] focus-visible:outline-2 focus-visible:outline-tsl12-accent focus-visible:-outline-offset-2"><span aria-hidden="true" class="w-[7px] h-[7px] mt-[7px] rounded-full bg-transparent group-data-unread:bg-tsl12-accent"></span><span class="min-w-0 grid gap-[3px]"><strong class="text-[13.5px] font-medium leading-tight group-data-unread:font-bold">Marcus Webb</strong><em class="not-italic text-[12.5px] leading-[1.45] text-tsl12-mut line-clamp-2">Re: Design tokens sync — agreed, let's move the semantic layer into the base package…</em></span><time class="mt-0.5 text-[11.5px] font-medium text-tsl12-mut">08:17</time></a>
<a href="#" role="option" aria-selected="false" data-unread class="group grid grid-cols-[auto_1fr_auto] items-start gap-2.5 px-3.5 py-3 border-b border-tsl12-line no-underline text-inherit transition-colors duration-150 hover:bg-tsl12-rowhover aria-selected:bg-tsl12-tint aria-selected:shadow-[inset_2.5px_0_0_var(--color-tsl12-accent)] focus-visible:outline-2 focus-visible:outline-tsl12-accent focus-visible:-outline-offset-2"><span aria-hidden="true" class="w-[7px] h-[7px] mt-[7px] rounded-full bg-transparent group-data-unread:bg-tsl12-accent"></span><span class="min-w-0 grid gap-[3px]"><strong class="text-[13.5px] font-medium leading-tight group-data-unread:font-bold">Linear</strong><em class="not-italic text-[12.5px] leading-[1.45] text-tsl12-mut line-clamp-2">Weekly digest: 14 issues closed, 3 in review. Cycle 42 completes Friday.</em></span><time class="mt-0.5 text-[11.5px] font-medium text-tsl12-mut">Mon</time></a>
<a href="#" role="option" aria-selected="false" class="group grid grid-cols-[auto_1fr_auto] items-start gap-2.5 px-3.5 py-3 border-b border-tsl12-line no-underline text-inherit transition-colors duration-150 hover:bg-tsl12-rowhover aria-selected:bg-tsl12-tint aria-selected:shadow-[inset_2.5px_0_0_var(--color-tsl12-accent)] focus-visible:outline-2 focus-visible:outline-tsl12-accent focus-visible:-outline-offset-2"><span aria-hidden="true" class="w-[7px] h-[7px] mt-[7px] rounded-full bg-transparent group-data-unread:bg-tsl12-accent"></span><span class="min-w-0 grid gap-[3px]"><strong class="text-[13.5px] font-medium leading-tight group-data-unread:font-bold">Amara Osei</strong><em class="not-italic text-[12.5px] leading-[1.45] text-tsl12-mut line-clamp-2">Accessibility audit results — great news on the focus-visible coverage, two items left…</em></span><time class="mt-0.5 text-[11.5px] font-medium text-tsl12-mut">Mon</time></a>
</div>
<main class="overflow-y-auto px-[38px] py-8 [scrollbar-gutter:stable]">
<h1 class="text-xl font-bold leading-snug">Re: Design tokens sync</h1>
<p class="mt-2.5 mb-[22px] text-[12.5px] font-medium text-tsl12-mut">Marcus Webb · to Design Platform · 08:17</p>
<p class="max-w-[60ch] mb-3.5 text-sm leading-[1.7] text-tsl12-body text-pretty">Agreed — let's move the semantic layer into the base package. The component tokens can then alias into it, which keeps theming to a single override point.</p>
<p class="max-w-[60ch] text-sm leading-[1.7] text-tsl12-body text-pretty">I'll draft the migration doc today. The mail list on your left scrolls independently of this pane — three scroll regions, one grid.</p>
</main>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tsl12-panel: #f3f4f6;
--color-tsl12-panel: oklch(0.965 0.005 240);
--color-tsl12-ink: #363b43;
--color-tsl12-ink: oklch(0.25 0.02 240);
--color-tsl12-body: #4c525c;
--color-tsl12-body: oklch(0.35 0.015 240);
--color-tsl12-mut: #71767f;
--color-tsl12-mut: oklch(0.5 0.015 240);
--color-tsl12-line: #dfe1e5;
--color-tsl12-line: oklch(0.89 0.007 240);
--color-tsl12-accent: #3f5fd0;
--color-tsl12-accent: oklch(0.52 0.17 255);
--color-tsl12-hover: rgb(54 59 67 / 0.05);
--color-tsl12-hover: color-mix(in oklab, oklch(0.25 0.02 240) 5%, transparent);
--color-tsl12-rowhover: rgb(54 59 67 / 0.035);
--color-tsl12-rowhover: color-mix(in oklab, oklch(0.25 0.02 240) 3.5%, transparent);
--color-tsl12-tint: rgb(63 95 208 / 0.1);
--color-tsl12-tint: color-mix(in oklab, oklch(0.52 0.17 255) 10%, transparent);
--font-tsl12-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}
.tsl-12 {
width: 100%;
height: 100svh;
min-height: 100svh;
color-scheme: light;
}
```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 Two-Pane Webmail Layout with List and Reading Pane
Source: https://codefronts.com/layouts/tailwind-css-sidebar-layouts/tailwind-two-pane-webmail-list-reading-layout/
The list/detail split every mail client, messenger and support inbox uses: folder rail, 320px message list, reading pane — three independent scroll regions in one grid. The port's pleasure is line-clamp-2, which replaces the four-declaration -webkit-box incantation with one utility.
## HTML
```html
<section class="tsl-12 w-full h-svh min-h-svh grid grid-cols-[200px_320px_1fr] font-tsl12-sans bg-white text-tsl12-ink">
<aside aria-label="Folders" class="bg-tsl12-panel border-r border-tsl12-line px-2.5 py-4 overflow-y-auto">
<button type="button" class="w-full min-h-11 mb-3.5 rounded-[10px] border-0 bg-tsl12-accent text-white text-[13.5px] font-semibold cursor-pointer transition-[filter] duration-200 hover:brightness-110 focus-visible:outline-2 focus-visible:outline-tsl12-accent focus-visible:outline-offset-2">Compose</button>
<nav class="flex flex-col gap-0.5">
<a href="#" aria-current="page" class="flex items-center justify-between min-h-11 px-3 rounded-[9px] text-[13.5px] font-medium text-tsl12-mut no-underline transition-colors duration-150 hover:text-tsl12-ink hover:bg-tsl12-hover aria-[current=page]:text-tsl12-ink aria-[current=page]:bg-tsl12-tint aria-[current=page]:font-semibold focus-visible:outline-2 focus-visible:outline-tsl12-accent focus-visible:outline-offset-1">Inbox <span class="text-[11.5px] font-semibold text-tsl12-accent">12</span></a>
<a href="#" class="flex items-center justify-between min-h-11 px-3 rounded-[9px] text-[13.5px] font-medium text-tsl12-mut no-underline transition-colors duration-150 hover:text-tsl12-ink hover:bg-tsl12-hover focus-visible:outline-2 focus-visible:outline-tsl12-accent focus-visible:outline-offset-1">Starred</a>
<a href="#" class="flex items-center justify-between min-h-11 px-3 rounded-[9px] text-[13.5px] font-medium text-tsl12-mut no-underline transition-colors duration-150 hover:text-tsl12-ink hover:bg-tsl12-hover focus-visible:outline-2 focus-visible:outline-tsl12-accent focus-visible:outline-offset-1">Sent</a>
<a href="#" class="flex items-center justify-between min-h-11 px-3 rounded-[9px] text-[13.5px] font-medium text-tsl12-mut no-underline transition-colors duration-150 hover:text-tsl12-ink hover:bg-tsl12-hover focus-visible:outline-2 focus-visible:outline-tsl12-accent focus-visible:outline-offset-1">Drafts <span class="text-[11.5px] font-semibold text-tsl12-accent">2</span></a>
<a href="#" class="flex items-center justify-between min-h-11 px-3 rounded-[9px] text-[13.5px] font-medium text-tsl12-mut no-underline transition-colors duration-150 hover:text-tsl12-ink hover:bg-tsl12-hover focus-visible:outline-2 focus-visible:outline-tsl12-accent focus-visible:outline-offset-1">Archive</a>
</nav>
</aside>
<div role="listbox" aria-label="Messages" class="border-r border-tsl12-line overflow-y-auto overscroll-contain [scrollbar-gutter:stable]">
<a href="#" role="option" aria-selected="false" data-unread class="group grid grid-cols-[auto_1fr_auto] items-start gap-2.5 px-3.5 py-3 border-b border-tsl12-line no-underline text-inherit transition-colors duration-150 hover:bg-tsl12-rowhover aria-selected:bg-tsl12-tint aria-selected:shadow-[inset_2.5px_0_0_var(--color-tsl12-accent)] focus-visible:outline-2 focus-visible:outline-tsl12-accent focus-visible:-outline-offset-2"><span aria-hidden="true" class="w-[7px] h-[7px] mt-[7px] rounded-full bg-transparent group-data-unread:bg-tsl12-accent"></span><span class="min-w-0 grid gap-[3px]"><strong class="text-[13.5px] font-medium leading-tight group-data-unread:font-bold">Priya Raman</strong><em class="not-italic text-[12.5px] leading-[1.45] text-tsl12-mut line-clamp-2">Q3 roadmap review — the container query rollout is ahead of schedule and I think we can…</em></span><time class="mt-0.5 text-[11.5px] font-medium text-tsl12-mut">09:41</time></a>
<a href="#" role="option" aria-selected="true" class="group grid grid-cols-[auto_1fr_auto] items-start gap-2.5 px-3.5 py-3 border-b border-tsl12-line no-underline text-inherit transition-colors duration-150 hover:bg-tsl12-rowhover aria-selected:bg-tsl12-tint aria-selected:shadow-[inset_2.5px_0_0_var(--color-tsl12-accent)] focus-visible:outline-2 focus-visible:outline-tsl12-accent focus-visible:-outline-offset-2"><span aria-hidden="true" class="w-[7px] h-[7px] mt-[7px] rounded-full bg-transparent group-data-unread:bg-tsl12-accent"></span><span class="min-w-0 grid gap-[3px]"><strong class="text-[13.5px] font-medium leading-tight group-data-unread:font-bold">Marcus Webb</strong><em class="not-italic text-[12.5px] leading-[1.45] text-tsl12-mut line-clamp-2">Re: Design tokens sync — agreed, let's move the semantic layer into the base package…</em></span><time class="mt-0.5 text-[11.5px] font-medium text-tsl12-mut">08:17</time></a>
<a href="#" role="option" aria-selected="false" data-unread class="group grid grid-cols-[auto_1fr_auto] items-start gap-2.5 px-3.5 py-3 border-b border-tsl12-line no-underline text-inherit transition-colors duration-150 hover:bg-tsl12-rowhover aria-selected:bg-tsl12-tint aria-selected:shadow-[inset_2.5px_0_0_var(--color-tsl12-accent)] focus-visible:outline-2 focus-visible:outline-tsl12-accent focus-visible:-outline-offset-2"><span aria-hidden="true" class="w-[7px] h-[7px] mt-[7px] rounded-full bg-transparent group-data-unread:bg-tsl12-accent"></span><span class="min-w-0 grid gap-[3px]"><strong class="text-[13.5px] font-medium leading-tight group-data-unread:font-bold">Linear</strong><em class="not-italic text-[12.5px] leading-[1.45] text-tsl12-mut line-clamp-2">Weekly digest: 14 issues closed, 3 in review. Cycle 42 completes Friday.</em></span><time class="mt-0.5 text-[11.5px] font-medium text-tsl12-mut">Mon</time></a>
<a href="#" role="option" aria-selected="false" class="group grid grid-cols-[auto_1fr_auto] items-start gap-2.5 px-3.5 py-3 border-b border-tsl12-line no-underline text-inherit transition-colors duration-150 hover:bg-tsl12-rowhover aria-selected:bg-tsl12-tint aria-selected:shadow-[inset_2.5px_0_0_var(--color-tsl12-accent)] focus-visible:outline-2 focus-visible:outline-tsl12-accent focus-visible:-outline-offset-2"><span aria-hidden="true" class="w-[7px] h-[7px] mt-[7px] rounded-full bg-transparent group-data-unread:bg-tsl12-accent"></span><span class="min-w-0 grid gap-[3px]"><strong class="text-[13.5px] font-medium leading-tight group-data-unread:font-bold">Amara Osei</strong><em class="not-italic text-[12.5px] leading-[1.45] text-tsl12-mut line-clamp-2">Accessibility audit results — great news on the focus-visible coverage, two items left…</em></span><time class="mt-0.5 text-[11.5px] font-medium text-tsl12-mut">Mon</time></a>
</div>
<main class="overflow-y-auto px-[38px] py-8 [scrollbar-gutter:stable]">
<h1 class="text-xl font-bold leading-snug">Re: Design tokens sync</h1>
<p class="mt-2.5 mb-[22px] text-[12.5px] font-medium text-tsl12-mut">Marcus Webb · to Design Platform · 08:17</p>
<p class="max-w-[60ch] mb-3.5 text-sm leading-[1.7] text-tsl12-body text-pretty">Agreed — let's move the semantic layer into the base package. The component tokens can then alias into it, which keeps theming to a single override point.</p>
<p class="max-w-[60ch] text-sm leading-[1.7] text-tsl12-body text-pretty">I'll draft the migration doc today. The mail list on your left scrolls independently of this pane — three scroll regions, one grid.</p>
</main>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tsl12-panel: #f3f4f6;
--color-tsl12-panel: oklch(0.965 0.005 240);
--color-tsl12-ink: #363b43;
--color-tsl12-ink: oklch(0.25 0.02 240);
--color-tsl12-body: #4c525c;
--color-tsl12-body: oklch(0.35 0.015 240);
--color-tsl12-mut: #71767f;
--color-tsl12-mut: oklch(0.5 0.015 240);
--color-tsl12-line: #dfe1e5;
--color-tsl12-line: oklch(0.89 0.007 240);
--color-tsl12-accent: #3f5fd0;
--color-tsl12-accent: oklch(0.52 0.17 255);
--color-tsl12-hover: rgb(54 59 67 / 0.05);
--color-tsl12-hover: color-mix(in oklab, oklch(0.25 0.02 240) 5%, transparent);
--color-tsl12-rowhover: rgb(54 59 67 / 0.035);
--color-tsl12-rowhover: color-mix(in oklab, oklch(0.25 0.02 240) 3.5%, transparent);
--color-tsl12-tint: rgb(63 95 208 / 0.1);
--color-tsl12-tint: color-mix(in oklab, oklch(0.52 0.17 255) 10%, transparent);
--font-tsl12-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}
.tsl-12 {
width: 100%;
height: 100svh;
min-height: 100svh;
color-scheme: light;
}
```How this works
State lives in attributes, and v4 has a variant for each. data-unread:font-bold on the sender, data-unread:bg-tsl12-accent on the dot, aria-selected:bg-tsl12-tint on the open row. The CSS original already used attribute selectors for exactly this reason; the port keeps the discipline and moves it onto the row, where a reviewer can check the pairing at a glance.
The preview clamp is the single biggest utility win in the collection. display:-webkit-box + -webkit-box-orient + -webkit-line-clamp + overflow:hidden is a four-part incantation where omitting any one part silently disables the whole thing. line-clamp-2 cannot be got wrong.
Make it yours
- Under 900px, hide the folder rail behind demo 08's drawer — every real mail client does exactly this.
- 320px is the industry-constant list width; 360px if you show avatars.
line-clamp-1for dense ops inboxes,line-clamp-3for support queues where context matters more than density.
Gotchas — read before shipping
- Each row should be ONE interactive element. Nesting buttons inside a clickable row creates unreachable controls.
min-w-0on the text column, or the preview refuses to truncate and pushes the timestamp off the row.- Never signal unread with colour alone — the dot plus the bold weight here is the WCAG-safe double signal.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
Line clamp shipped prefixed everywhere years ago and Tailwind emits the prefixes; the rest is baseline grid.