30 CSS Sidebar Layouts27 / 30

Pure CSSMIT licensed

Pure CSS Resizable Sidebar Layout Using Resize Property

A drag-to-resize sidebar with no mousemove listeners, no pointer capture, no drag math: the CSS resize property does the entire interaction. min/max-width fence the range, a styled grip zone signals the affordance, and the main pane reflows live because the layout is plain flex — the browser is running the drag loop for you, off the main thread.

Published Updated

Live Demo
Try it

The code

<section class="sbl-27">
  <aside class="sbl-27__rail" aria-label="Notes">
    <div class="sbl-27__inner">
      <h2>Notebook</h2>
      <nav>
        <a href="#" aria-current="page">Container queries field guide</a><a href="#">Subgrid recipes</a><a href="#">The :has() cookbook</a><a href="#">Anchor positioning notes</a><a href="#">View transitions research</a>
      </nav>
    </div>
    <span class="sbl-27__grip" aria-hidden="true"><i></i><i></i><i></i></span>
  </aside>
  <main class="sbl-27__main"><h1>Container queries field guide</h1><p>Drag the sidebar's bottom-right corner (or along the dotted grip) — resize:horizontal + a min/max fence, zero drag JavaScript. The pane you're reading reflows live.</p></main>
</section>
.sbl-27,
.sbl-27 *,
.sbl-27 *::before,
.sbl-27 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.sbl-27 {
  --panel: oklch(0.17 0.014 85);
  --ink: oklch(0.94 0.006 85);
  --mut: oklch(0.66 0.015 85);
  --line: oklch(0.29 0.018 85);
  --accent: oklch(0.8 0.13 90);
  display: flex;
  height: 100vh;
  height: 100dvh;
  font-family: system-ui,'Segoe UI',sans-serif;
  background: oklch(0.13 0.012 85);
  color: var(--ink);
}

.sbl-27__rail {
  position: relative;
  resize: horizontal;
  overflow: auto;
  min-width: 200px;
  max-width: 440px;
  width: 280px;
  flex: none;
  background: var(--panel);
  border-right: 1px solid var(--line);
}

.sbl-27__inner {
  padding: 20px 14px;
  min-width: 186px;
}

.sbl-27__rail h2 {
  font: 700 14.5px/1 system-ui,sans-serif;
  padding: 0 10px 14px;
}

.sbl-27__rail nav {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.sbl-27__rail a {
  padding: 10px 12px;
  border-radius: 9px;
  font: 500 13.5px/1.35 system-ui,sans-serif;
  color: var(--mut);
  text-decoration: none;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  transition: background-color .15s ease,color .15s ease;
}

.sbl-27__rail a:hover {
  color: var(--ink);
  background: color-mix(in oklab,var(--ink) 6%,transparent);
}

.sbl-27__rail a[aria-current="page"] {
  color: var(--accent);
  background: color-mix(in oklab,var(--accent) 13%,transparent);
}

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

.sbl-27__grip {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  width: 14px;
  display: flex;
  flex-direction: column;
  gap: 3px;
  align-items: center;
  justify-content: center;
  cursor: col-resize;
  opacity: .5;
  transition: opacity .2s ease;
}

.sbl-27__grip:hover {
  opacity: 1;
}

.sbl-27__grip i {
  width: 3px;
  height: 3px;
  border-radius: 50%;
  background: var(--mut);
}

.sbl-27__main {
  flex: 1;
  min-width: 0;
  padding: 32px;
  overflow-y: auto;
}

.sbl-27__main h1 {
  font: 600 22px/1.2 system-ui,sans-serif;
  margin-bottom: 10px;
}

.sbl-27__main p {
  color: var(--mut);
  font-size: 14px;
  line-height: 1.65;
  max-width: 54ch;
  text-wrap: pretty;
}

@media (pointer: coarse) {
  .sbl-27__grip {
    width: 22px;
  }
}

@media (prefers-reduced-motion: reduce) {
  .sbl-27__grip,
    .sbl-27__rail a {
    transition: none;
  }
}
/* No JavaScript — resize:horizontal + overflow:auto + a min/max-width fence; the browser runs the entire drag interaction natively. */
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: Pure CSS Resizable Sidebar Layout Using Resize Property
Source: https://codefronts.com/layouts/css-sidebar-layouts/resizable-sidebar/

A drag-to-resize sidebar with no mousemove listeners, no pointer capture, no drag math: the CSS resize property does the entire interaction. min/max-width fence the range, a styled grip zone signals the affordance, and the main pane reflows live because the layout is plain flex — the browser is running the drag loop for you, off the main thread.
## HTML
```html
<section class="sbl-27">
  <aside class="sbl-27__rail" aria-label="Notes">
    <div class="sbl-27__inner">
      <h2>Notebook</h2>
      <nav>
        <a href="#" aria-current="page">Container queries field guide</a><a href="#">Subgrid recipes</a><a href="#">The :has() cookbook</a><a href="#">Anchor positioning notes</a><a href="#">View transitions research</a>
      </nav>
    </div>
    <span class="sbl-27__grip" aria-hidden="true"><i></i><i></i><i></i></span>
  </aside>
  <main class="sbl-27__main"><h1>Container queries field guide</h1><p>Drag the sidebar's bottom-right corner (or along the dotted grip) — resize:horizontal + a min/max fence, zero drag JavaScript. The pane you're reading reflows live.</p></main>
</section>
```
## CSS
```css
.sbl-27,
.sbl-27 *,
.sbl-27 *::before,
.sbl-27 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.sbl-27 {
  --panel: oklch(0.17 0.014 85);
  --ink: oklch(0.94 0.006 85);
  --mut: oklch(0.66 0.015 85);
  --line: oklch(0.29 0.018 85);
  --accent: oklch(0.8 0.13 90);
  display: flex;
  height: 100vh;
  height: 100dvh;
  font-family: system-ui,'Segoe UI',sans-serif;
  background: oklch(0.13 0.012 85);
  color: var(--ink);
}

.sbl-27__rail {
  position: relative;
  resize: horizontal;
  overflow: auto;
  min-width: 200px;
  max-width: 440px;
  width: 280px;
  flex: none;
  background: var(--panel);
  border-right: 1px solid var(--line);
}

.sbl-27__inner {
  padding: 20px 14px;
  min-width: 186px;
}

.sbl-27__rail h2 {
  font: 700 14.5px/1 system-ui,sans-serif;
  padding: 0 10px 14px;
}

.sbl-27__rail nav {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.sbl-27__rail a {
  padding: 10px 12px;
  border-radius: 9px;
  font: 500 13.5px/1.35 system-ui,sans-serif;
  color: var(--mut);
  text-decoration: none;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  transition: background-color .15s ease,color .15s ease;
}

.sbl-27__rail a:hover {
  color: var(--ink);
  background: color-mix(in oklab,var(--ink) 6%,transparent);
}

.sbl-27__rail a[aria-current="page"] {
  color: var(--accent);
  background: color-mix(in oklab,var(--accent) 13%,transparent);
}

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

.sbl-27__grip {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  width: 14px;
  display: flex;
  flex-direction: column;
  gap: 3px;
  align-items: center;
  justify-content: center;
  cursor: col-resize;
  opacity: .5;
  transition: opacity .2s ease;
}

.sbl-27__grip:hover {
  opacity: 1;
}

.sbl-27__grip i {
  width: 3px;
  height: 3px;
  border-radius: 50%;
  background: var(--mut);
}

.sbl-27__main {
  flex: 1;
  min-width: 0;
  padding: 32px;
  overflow-y: auto;
}

.sbl-27__main h1 {
  font: 600 22px/1.2 system-ui,sans-serif;
  margin-bottom: 10px;
}

.sbl-27__main p {
  color: var(--mut);
  font-size: 14px;
  line-height: 1.65;
  max-width: 54ch;
  text-wrap: pretty;
}

@media (pointer: coarse) {
  .sbl-27__grip {
    width: 22px;
  }
}

@media (prefers-reduced-motion: reduce) {
  .sbl-27__grip,
    .sbl-27__rail a {
    transition: none;
  }
}
```

## JavaScript
```js
/* No JavaScript — resize:horizontal + overflow:auto + a min/max-width fence; the browser runs the entire drag interaction natively. */
```

How this works

Three declarations create the interaction: resize:horizontal; overflow:auto (resize only activates on elements with non-visible overflow) and the fence — min-width:200px; max-width:440px. The user drags the bottom-right handle and the browser does hit-testing, cursor, drag tracking and relayout natively. The main pane is flex:1, so it absorbs every width change in the same frame.

The stock handle is a tiny corner triangle, so the demo builds a visible affordance: a full-height grip strip on the sidebar's right edge (three dots, cursor:col-resize) that visually extends the native handle's hit area. Honest limitation, documented: the native handle remains the true drag target at the bottom-right corner — for a full-edge drag you graduate to ~15 lines of pointer-event JS. In exchange, this version costs zero script, works with the keyboard in some browsers, and can't ever leak a stuck-drag state. A @media (pointer:coarse) rule widens the grip on touch devices.

Make it yours

  • The fence IS the design: min 200px keeps nav labels whole; max 440px stops the sidebar from eating the app.
  • Persist the chosen width with a ResizeObserver + localStorage (6 lines) if users expect it remembered.
  • resize:vertical on horizontal split panes (bottom terminals, log viewers) — the same three declarations rotated.
  • Style the corner handle itself in WebKit via ::-webkit-resizer for a brand-colored grip.

Gotchas — read before shipping

  • resize does nothing without overflow set — overflow:auto (or hidden/scroll) is half the feature and everyone forgets it.
  • The element resizes its CONTENT BOX: with border-box sizing, min/max include padding — plan the fence values accordingly.
  • Firefox and Chrome disagree on handle placement in RTL contexts; test if you ship RTL.

Browser support

ChromeSafariFirefoxEdge
114+17.5+121+114+

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

resize is fifteen years old and universal on desktop. It's inert on touch-only devices — the JS upgrade path is the answer where touch resize is a requirement.

Techniques used in this demo

Search CodeFronts

Loading…