30 CSS Sidebar Layouts
12 / 30

All 30 in 30 CSS Sidebar Layouts

  1. 01Modern SaaS Dashboard Sidebar Menu with Native Dark Mode Switch
  2. 02Accessible Admin Panel Sidebar with CSS Tooltips and Icon Collapse
  3. 03Developer Documentation Sidebar with Scroll-Synced Nesting
  4. 04Mobile-First Responsive Sidebar to Bottom Navigation Switch Layout
  5. 05Data Analytics Sidebar Layout with CSS Subgrid Alignment
  6. 06Smooth Animated Collapsible Sidebar Menu with Icon-Only Mode
  7. 07CSS Position Sticky Sidebar Layout for E-Commerce and Blogs
  8. 08Pure CSS Collapsible Sidebar Drawer Menu without JavaScript
  9. 09CSS Fixed Sidebar Layout with Independent Scrollable Main Content
  10. 10Off-Canvas Navigation Drawer for Mobile & Desktop Web Apps
  11. 11Right-Aligned CSS Sidebar Layout for Secondary Tool Panels
  12. 12Two-Pane Webmail UI Layout with CSS Flexbox & Grid
  13. 13Dual Sidebar App Shell Layout with Left Nav and Right Context Panel
  14. 14Compact Mini Icon Rail Sidebar Navigation for SaaS Web Apps
  15. 15Responsive E-Commerce Product Filter Sidebar with Native Accordion Menu
  16. 16Modern Floating Glassmorphism Sidebar UI Component
  17. 17CSS Sidebar Menu with Sticky User Profile Footer
  18. 18Push Content Sidebar Navigation Layout with Smooth CSS Transitions
  19. 19Off-Canvas Sidebar Overlay Layout with Blur Backdrop
  20. 20Persistent Collapsible State Sidebar with CSS Variables
  21. 21App Sidebar Navigation with Integrated Sticky Search Bar
  22. 22Section-Grouped Sidebar Navigation Menu with Header Labels
  23. 23CSS Target Active Indicator Sidebar Menu without JavaScript
  24. 24Multi-Level Nested Tree Sidebar Navigation Layout
  25. 25Multi-Tab Sidebar UI Component with Dynamic Panel Switching
  26. 26Onboarding Wizard Sidebar Layout with Step Progress Tracking
  27. 27Pure CSS Resizable Sidebar Layout Using Resize Property
  28. 28Three-Pane Productivity Workspace Shell Layout
  29. 29Next-Gen Adaptive Sidebar Layout Using CSS Container Queries
  30. 30Frosted Glass Floating Sidebar Layout with Backdrop Blur
Pure CSSMIT licensed

Two-Pane Webmail UI Layout with CSS Flexbox & Grid

The classic list/detail split every mail client, messenger and support inbox uses: a fixed folder rail, a 320px scrollable message list with unread indicators, and a reading pane — three independent scroll regions in one grid, with the selected message tinted via aria-selected and unread rows styled from a data attribute.

Live Demo
Plain CSSTailwind v4
Try it

Mosaic by Cruip: a complete Tailwind admin dashboard template in HTML, React, Next.js and Vue · $69

Cruip · Mosaic by Cruip · Affiliate link

View Mosaic dashboard

The code

<section class="sbl-12">
  <aside class="sbl-12__folders" aria-label="Folders">
    <button class="sbl-12__compose" type="button">Compose</button>
    <nav><a href="#" aria-current="page">Inbox <span>12</span></a><a href="#">Starred</a><a href="#">Sent</a><a href="#">Drafts <span>2</span></a><a href="#">Archive</a></nav>
  </aside>
  <div class="sbl-12__list" role="listbox" aria-label="Messages">
    <a class="sbl-12__msg" href="#" data-unread role="option" aria-selected="false"><span class="sbl-12__dot" aria-hidden="true"></span><span class="sbl-12__body"><strong>Priya Raman</strong><em>Q3 roadmap review — the container query rollout is ahead of schedule and I think we can…</em></span><time>09:41</time></a>
    <a class="sbl-12__msg" href="#" role="option" aria-selected="true"><span class="sbl-12__dot" aria-hidden="true"></span><span class="sbl-12__body"><strong>Marcus Webb</strong><em>Re: Design tokens sync — agreed, let's move the semantic layer into the base package…</em></span><time>08:17</time></a>
    <a class="sbl-12__msg" href="#" data-unread role="option" aria-selected="false"><span class="sbl-12__dot" aria-hidden="true"></span><span class="sbl-12__body"><strong>Linear</strong><em>Weekly digest: 14 issues closed, 3 in review. Cycle 42 completes Friday.</em></span><time>Mon</time></a>
    <a class="sbl-12__msg" href="#" role="option" aria-selected="false"><span class="sbl-12__dot" aria-hidden="true"></span><span class="sbl-12__body"><strong>Amara Osei</strong><em>Accessibility audit results — great news on the focus-visible coverage, two items left…</em></span><time>Mon</time></a>
  </div>
  <main class="sbl-12__read">
    <h1>Re: Design tokens sync</h1>
    <p class="sbl-12__meta">Marcus Webb · to Design Platform · 08:17</p>
    <p>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>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>
.sbl-12,
.sbl-12 *,
.sbl-12 *::before,
.sbl-12 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.sbl-12 {
  --panel: oklch(0.965 0.005 240);
  --ink: oklch(0.25 0.02 240);
  --mut: oklch(0.5 0.015 240);
  --line: oklch(0.89 0.007 240);
  --accent: oklch(0.52 0.17 255);
  display: grid;
  grid-template-columns: 200px 320px 1fr;
  height: 100vh;
  height: 100dvh;
  font-family: system-ui,'Segoe UI',sans-serif;
  background: #fff;
  color: var(--ink);
}

.sbl-12__folders {
  background: var(--panel);
  border-right: 1px solid var(--line);
  padding: 16px 10px;
  overflow-y: auto;
}

.sbl-12__compose {
  width: 100%;
  padding: 11px;
  border: none;
  border-radius: 10px;
  background: var(--accent);
  color: #fff;
  font: 600 13.5px/1 system-ui,sans-serif;
  cursor: pointer;
  margin-bottom: 14px;
  transition: filter .2s ease;
}

.sbl-12__compose:hover {
  filter: brightness(1.1);
}

.sbl-12__compose:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.sbl-12__folders nav {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.sbl-12__folders a {
  display: flex;
  justify-content: space-between;
  padding: 9px 12px;
  border-radius: 9px;
  font: 500 13.5px/1 system-ui,sans-serif;
  color: var(--mut);
  text-decoration: none;
}

.sbl-12__folders a span {
  font-size: 11.5px;
  color: var(--accent);
  font-weight: 600;
}

.sbl-12__folders a:hover {
  background: color-mix(in oklab,var(--ink) 5%,transparent);
  color: var(--ink);
}

.sbl-12__folders a[aria-current="page"] {
  background: color-mix(in oklab,var(--accent) 11%,transparent);
  color: var(--ink);
  font-weight: 600;
}

.sbl-12__folders a:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

.sbl-12__list {
  border-right: 1px solid var(--line);
  overflow-y: auto;
  overscroll-behavior: contain;
}

.sbl-12__msg {
  display: grid;
  grid-template-columns: auto 1fr auto;
  gap: 10px;
  align-items: start;
  padding: 13px 14px;
  border-bottom: 1px solid var(--line);
  text-decoration: none;
  color: inherit;
  transition: background-color .15s ease;
}

.sbl-12__msg:hover {
  background: color-mix(in oklab,var(--ink) 3.5%,transparent);
}

.sbl-12__msg[aria-selected="true"] {
  background: color-mix(in oklab,var(--accent) 9%,transparent);
  box-shadow: inset 2.5px 0 0 var(--accent);
}

.sbl-12__msg:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

.sbl-12__dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  margin-top: 5px;
  background: transparent;
}

.sbl-12__msg[data-unread] .sbl-12__dot {
  background: var(--accent);
}

.sbl-12__body {
  min-width: 0;
  display: grid;
  gap: 3px;
}

.sbl-12__body strong {
  font: 500 13.5px/1.2 system-ui,sans-serif;
}

.sbl-12__msg[data-unread] .sbl-12__body strong {
  font-weight: 700;
}

.sbl-12__body em {
  font: 400 12.5px/1.45 system-ui,sans-serif;
  font-style: normal;
  color: var(--mut);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.sbl-12__msg time {
  font: 500 11.5px/1 system-ui,sans-serif;
  color: var(--mut);
  margin-top: 2px;
}

.sbl-12__read {
  overflow-y: auto;
  padding: 32px 38px;
}

.sbl-12__read h1 {
  font: 650 20px/1.25 system-ui,sans-serif;
}

.sbl-12__meta {
  font: 500 12.5px/1 system-ui,sans-serif;
  color: var(--mut);
  margin: 10px 0 22px;
}

.sbl-12__read p {
  font-size: 14px;
  line-height: 1.7;
  max-width: 60ch;
  color: oklch(0.35 0.015 240);
  margin-bottom: 14px;
  text-wrap: pretty;
}

@media (prefers-reduced-motion: reduce) {
  .sbl-12__msg,
    .sbl-12__compose {
    transition: none;
  }
}
/* No JavaScript — three independent scroll panes in one grid; unread and selected states style from data-unread and aria-selected attributes. */
Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
Here's a working CSS Sidebar Layout 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: Two-Pane Webmail UI Layout with CSS Flexbox & Grid
Source: https://codefronts.com/layouts/css-sidebar-layouts/two-pane-mail/

The classic list/detail split every mail client, messenger and support inbox uses: a fixed folder rail, a 320px scrollable message list with unread indicators, and a reading pane — three independent scroll regions in one grid, with the selected message tinted via aria-selected and unread rows styled from a data attribute.
## HTML
```html
<section class="sbl-12">
  <aside class="sbl-12__folders" aria-label="Folders">
    <button class="sbl-12__compose" type="button">Compose</button>
    <nav><a href="#" aria-current="page">Inbox <span>12</span></a><a href="#">Starred</a><a href="#">Sent</a><a href="#">Drafts <span>2</span></a><a href="#">Archive</a></nav>
  </aside>
  <div class="sbl-12__list" role="listbox" aria-label="Messages">
    <a class="sbl-12__msg" href="#" data-unread role="option" aria-selected="false"><span class="sbl-12__dot" aria-hidden="true"></span><span class="sbl-12__body"><strong>Priya Raman</strong><em>Q3 roadmap review — the container query rollout is ahead of schedule and I think we can…</em></span><time>09:41</time></a>
    <a class="sbl-12__msg" href="#" role="option" aria-selected="true"><span class="sbl-12__dot" aria-hidden="true"></span><span class="sbl-12__body"><strong>Marcus Webb</strong><em>Re: Design tokens sync — agreed, let's move the semantic layer into the base package…</em></span><time>08:17</time></a>
    <a class="sbl-12__msg" href="#" data-unread role="option" aria-selected="false"><span class="sbl-12__dot" aria-hidden="true"></span><span class="sbl-12__body"><strong>Linear</strong><em>Weekly digest: 14 issues closed, 3 in review. Cycle 42 completes Friday.</em></span><time>Mon</time></a>
    <a class="sbl-12__msg" href="#" role="option" aria-selected="false"><span class="sbl-12__dot" aria-hidden="true"></span><span class="sbl-12__body"><strong>Amara Osei</strong><em>Accessibility audit results — great news on the focus-visible coverage, two items left…</em></span><time>Mon</time></a>
  </div>
  <main class="sbl-12__read">
    <h1>Re: Design tokens sync</h1>
    <p class="sbl-12__meta">Marcus Webb · to Design Platform · 08:17</p>
    <p>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>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
.sbl-12,
.sbl-12 *,
.sbl-12 *::before,
.sbl-12 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.sbl-12 {
  --panel: oklch(0.965 0.005 240);
  --ink: oklch(0.25 0.02 240);
  --mut: oklch(0.5 0.015 240);
  --line: oklch(0.89 0.007 240);
  --accent: oklch(0.52 0.17 255);
  display: grid;
  grid-template-columns: 200px 320px 1fr;
  height: 100vh;
  height: 100dvh;
  font-family: system-ui,'Segoe UI',sans-serif;
  background: #fff;
  color: var(--ink);
}

.sbl-12__folders {
  background: var(--panel);
  border-right: 1px solid var(--line);
  padding: 16px 10px;
  overflow-y: auto;
}

.sbl-12__compose {
  width: 100%;
  padding: 11px;
  border: none;
  border-radius: 10px;
  background: var(--accent);
  color: #fff;
  font: 600 13.5px/1 system-ui,sans-serif;
  cursor: pointer;
  margin-bottom: 14px;
  transition: filter .2s ease;
}

.sbl-12__compose:hover {
  filter: brightness(1.1);
}

.sbl-12__compose:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.sbl-12__folders nav {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.sbl-12__folders a {
  display: flex;
  justify-content: space-between;
  padding: 9px 12px;
  border-radius: 9px;
  font: 500 13.5px/1 system-ui,sans-serif;
  color: var(--mut);
  text-decoration: none;
}

.sbl-12__folders a span {
  font-size: 11.5px;
  color: var(--accent);
  font-weight: 600;
}

.sbl-12__folders a:hover {
  background: color-mix(in oklab,var(--ink) 5%,transparent);
  color: var(--ink);
}

.sbl-12__folders a[aria-current="page"] {
  background: color-mix(in oklab,var(--accent) 11%,transparent);
  color: var(--ink);
  font-weight: 600;
}

.sbl-12__folders a:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

.sbl-12__list {
  border-right: 1px solid var(--line);
  overflow-y: auto;
  overscroll-behavior: contain;
}

.sbl-12__msg {
  display: grid;
  grid-template-columns: auto 1fr auto;
  gap: 10px;
  align-items: start;
  padding: 13px 14px;
  border-bottom: 1px solid var(--line);
  text-decoration: none;
  color: inherit;
  transition: background-color .15s ease;
}

.sbl-12__msg:hover {
  background: color-mix(in oklab,var(--ink) 3.5%,transparent);
}

.sbl-12__msg[aria-selected="true"] {
  background: color-mix(in oklab,var(--accent) 9%,transparent);
  box-shadow: inset 2.5px 0 0 var(--accent);
}

.sbl-12__msg:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

.sbl-12__dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  margin-top: 5px;
  background: transparent;
}

.sbl-12__msg[data-unread] .sbl-12__dot {
  background: var(--accent);
}

.sbl-12__body {
  min-width: 0;
  display: grid;
  gap: 3px;
}

.sbl-12__body strong {
  font: 500 13.5px/1.2 system-ui,sans-serif;
}

.sbl-12__msg[data-unread] .sbl-12__body strong {
  font-weight: 700;
}

.sbl-12__body em {
  font: 400 12.5px/1.45 system-ui,sans-serif;
  font-style: normal;
  color: var(--mut);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.sbl-12__msg time {
  font: 500 11.5px/1 system-ui,sans-serif;
  color: var(--mut);
  margin-top: 2px;
}

.sbl-12__read {
  overflow-y: auto;
  padding: 32px 38px;
}

.sbl-12__read h1 {
  font: 650 20px/1.25 system-ui,sans-serif;
}

.sbl-12__meta {
  font: 500 12.5px/1 system-ui,sans-serif;
  color: var(--mut);
  margin: 10px 0 22px;
}

.sbl-12__read p {
  font-size: 14px;
  line-height: 1.7;
  max-width: 60ch;
  color: oklch(0.35 0.015 240);
  margin-bottom: 14px;
  text-wrap: pretty;
}

@media (prefers-reduced-motion: reduce) {
  .sbl-12__msg,
    .sbl-12__compose {
    transition: none;
  }
}
```

## JavaScript
```js
/* No JavaScript — three independent scroll panes in one grid; unread and selected states style from data-unread and aria-selected attributes. */
```

How this works

The shell is grid-template-columns:200px 320px 1fr with height:100dvh, and each pane carries its own overflow-y:auto — the same independence trick as demo 08 but three-wide. The message list is where the technique lives: rows are a nested grid (auto 1fr auto — unread dot, sender/preview stack, timestamp) so timestamps right-align regardless of preview length, and previews truncate with the two-line clamp (display:-webkit-box; -webkit-line-clamp:2) rather than a single ellipsis, which is how modern clients balance density and scannability.

State is expressed in attributes, not classes: data-unread drives the bold sender and accent dot ([data-unread] .sender{font-weight:700}), and the open message row carries aria-selected="true" — the same attribute a screen reader announces — styled as the tinted card. Attribute-driven styling keeps the markup honest: the accessibility tree and the visual state can't drift apart because they're the same data.

Make it yours

  • Collapse to two panes under 900px by hiding the folder rail behind demo 07's drawer — mail clients all do exactly this.
  • The 320px list is the industry constant; 360px if you show avatars.
  • Swap -webkit-line-clamp for the unprefixed line-clamp as support lands — ship both, prefixed first.
  • Add scrollbar-gutter:stable to the list so hover-revealed scrollbars don't nudge the rows.

Gotchas — read before shipping

  • Each message row should be ONE interactive element (a link or button) — nesting buttons inside a clickable row creates unreachable controls.
  • -webkit-line-clamp requires the full incantation: display:-webkit-box + -webkit-box-orient:vertical + overflow:hidden; missing any one silently disables it.
  • Don't fake unread with color alone — the dot plus bold weight here is the WCAG-safe double signal.

Browser support

ChromeSafariFirefoxEdge
114+17.5+121+114+

Floor set by oklch(), color-mix(), text-wrap as this demo is written. The core technique itself goes back further — Chrome 90+.

Line-clamp shipped prefixed everywhere years ago; the rest is baseline grid. Runs effectively everywhere.

Techniques used in this demo

Search CodeFronts

Loading…