30 CSS Sidebar Layouts29 / 30
Next-Gen Adaptive Sidebar Layout Using CSS Container Queries
A sidebar that responds to ITS OWN width, not the viewport: drag the demo's resize handle and watch the nav morph through three densities — full labels+meta, labels only, icon rail — driven entirely by @container rules. Drop this component into any column of any app and it self-adapts; media queries could never know.
Published Updated
The code
<section class="sbl-29">
<div class="sbl-29__frame">
<aside class="sbl-29__rail" aria-label="Library">
<div class="sbl-29__brand"><span class="sbl-29__mark" aria-hidden="true"></span><span class="sbl-29__name">Shelfspace</span></div>
<nav class="sbl-29__nav">
<a href="#" aria-current="page"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20V2H6.5A2.5 2.5 0 0 0 4 4.5zM4 19.5A2.5 2.5 0 0 0 6.5 22H20v-5"/></svg><span class="sbl-29__lbl">Reading now</span><small class="sbl-29__meta">3</small></a>
<a href="#"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6 2h12a2 2 0 0 1 2 2v18l-8-4-8 4V4a2 2 0 0 1 2-2z"/></svg><span class="sbl-29__lbl">Saved</span><small class="sbl-29__meta">47</small></a>
<a href="#"><svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3.5 2"/></svg><span class="sbl-29__lbl">History</span><small class="sbl-29__meta">210</small></a>
<a href="#"><svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="3"/><path d="M12 2v3m0 14v3M2 12h3m14 0h3M4.9 4.9l2.1 2.1m10 10 2.1 2.1m0-14.2-2.1 2.1m-10 10-2.1 2.1"/></svg><span class="sbl-29__lbl">Settings</span></a>
</nav>
</aside>
<span class="sbl-29__grip" aria-hidden="true"><i></i><i></i><i></i></span>
</div>
<main class="sbl-29__main"><h1>Reading now</h1><p>Drag the dotted edge. Full → labels-only → icon rail: three densities from @container rules — the sidebar reads its OWN width, never the viewport's.</p></main>
</section><section class="sbl-29">
<div class="sbl-29__frame">
<aside class="sbl-29__rail" aria-label="Library">
<div class="sbl-29__brand"><span class="sbl-29__mark" aria-hidden="true"></span><span class="sbl-29__name">Shelfspace</span></div>
<nav class="sbl-29__nav">
<a href="#" aria-current="page"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20V2H6.5A2.5 2.5 0 0 0 4 4.5zM4 19.5A2.5 2.5 0 0 0 6.5 22H20v-5"/></svg><span class="sbl-29__lbl">Reading now</span><small class="sbl-29__meta">3</small></a>
<a href="#"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6 2h12a2 2 0 0 1 2 2v18l-8-4-8 4V4a2 2 0 0 1 2-2z"/></svg><span class="sbl-29__lbl">Saved</span><small class="sbl-29__meta">47</small></a>
<a href="#"><svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3.5 2"/></svg><span class="sbl-29__lbl">History</span><small class="sbl-29__meta">210</small></a>
<a href="#"><svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="3"/><path d="M12 2v3m0 14v3M2 12h3m14 0h3M4.9 4.9l2.1 2.1m10 10 2.1 2.1m0-14.2-2.1 2.1m-10 10-2.1 2.1"/></svg><span class="sbl-29__lbl">Settings</span></a>
</nav>
</aside>
<span class="sbl-29__grip" aria-hidden="true"><i></i><i></i><i></i></span>
</div>
<main class="sbl-29__main"><h1>Reading now</h1><p>Drag the dotted edge. Full → labels-only → icon rail: three densities from @container rules — the sidebar reads its OWN width, never the viewport's.</p></main>
</section>.sbl-29,
.sbl-29 *,
.sbl-29 *::before,
.sbl-29 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.sbl-29 {
--panel: oklch(0.17 0.016 155);
--ink: oklch(0.94 0.005 155);
--mut: oklch(0.64 0.013 155);
--line: oklch(0.28 0.017 155);
--accent: oklch(0.78 0.15 160);
display: flex;
height: 100vh;
height: 100dvh;
font-family: system-ui,'Segoe UI',sans-serif;
background: oklch(0.13 0.012 155);
color: var(--ink);
}
.sbl-29__frame {
position: relative;
flex: none;
resize: horizontal;
overflow: auto;
width: 264px;
min-width: 76px;
max-width: 340px;
container: sbl29/inline-size;
background: var(--panel);
border-right: 1px solid var(--line);
}
.sbl-29__rail {
padding: 16px 10px;
min-height: 100%;
}
.sbl-29__brand {
display: flex;
align-items: center;
gap: 10px;
padding: 4px 10px 16px;
overflow: hidden;
}
.sbl-29__mark {
flex: none;
width: 28px;
height: 28px;
border-radius: 9px;
background: conic-gradient(from 250deg,var(--accent),oklch(0.6 0.14 220));
}
.sbl-29__name {
font: 700 clamp(13px,6cqi,16px)/1 system-ui,sans-serif;
white-space: nowrap;
}
.sbl-29__nav {
display: flex;
flex-direction: column;
gap: 3px;
}
.sbl-29__nav a {
display: flex;
align-items: center;
gap: 11px;
min-height: 44px;
padding: 0 12px;
border-radius: 11px;
font: 500 14px/1 system-ui,sans-serif;
color: var(--mut);
text-decoration: none;
white-space: nowrap;
transition: background-color .18s ease,color .18s ease;
}
.sbl-29__nav svg {
flex: none;
width: 19px;
height: 19px;
fill: none;
stroke: currentColor;
stroke-width: 1.8;
stroke-linecap: round;
stroke-linejoin: round;
}
.sbl-29__nav a:hover {
color: var(--ink);
background: color-mix(in oklab,var(--ink) 6%,transparent);
}
.sbl-29__nav a[aria-current="page"] {
color: var(--accent);
background: color-mix(in oklab,var(--accent) 14%,transparent);
}
.sbl-29__nav a:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
.sbl-29__meta {
margin-left: auto;
font: 600 11px/1 system-ui,sans-serif;
color: var(--mut);
background: color-mix(in oklab,var(--ink) 8%,transparent);
border-radius: 999px;
padding: 3px 8px;
}
@container sbl29 (max-width: 220px) {
.sbl-29__meta {
display: none;
}
}
@container sbl29 (max-width: 120px) {
.sbl-29__lbl,
.sbl-29__name {
display: none;
}
.sbl-29__nav a {
justify-content: center;
padding: 0;
width: 44px;
margin-inline: auto;
}
.sbl-29__brand {
justify-content: center;
padding-inline: 0;
}
}
.sbl-29__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;
}
.sbl-29__grip:hover {
opacity: 1;
}
.sbl-29__grip i {
width: 3px;
height: 3px;
border-radius: 50%;
background: var(--mut);
}
.sbl-29 > .sbl-29__frame + .sbl-29__main,
.sbl-29__main {
flex: 1;
min-width: 0;
padding: 32px;
overflow-y: auto;
}
.sbl-29__main h1 {
font: 600 22px/1.2 system-ui,sans-serif;
margin-bottom: 10px;
}
.sbl-29__main p {
color: var(--mut);
font-size: 14px;
line-height: 1.65;
max-width: 50ch;
text-wrap: pretty;
}
@media (prefers-reduced-motion: reduce) {
.sbl-29__nav a {
transition: none;
}
}.sbl-29,
.sbl-29 *,
.sbl-29 *::before,
.sbl-29 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.sbl-29 {
--panel: oklch(0.17 0.016 155);
--ink: oklch(0.94 0.005 155);
--mut: oklch(0.64 0.013 155);
--line: oklch(0.28 0.017 155);
--accent: oklch(0.78 0.15 160);
display: flex;
height: 100vh;
height: 100dvh;
font-family: system-ui,'Segoe UI',sans-serif;
background: oklch(0.13 0.012 155);
color: var(--ink);
}
.sbl-29__frame {
position: relative;
flex: none;
resize: horizontal;
overflow: auto;
width: 264px;
min-width: 76px;
max-width: 340px;
container: sbl29/inline-size;
background: var(--panel);
border-right: 1px solid var(--line);
}
.sbl-29__rail {
padding: 16px 10px;
min-height: 100%;
}
.sbl-29__brand {
display: flex;
align-items: center;
gap: 10px;
padding: 4px 10px 16px;
overflow: hidden;
}
.sbl-29__mark {
flex: none;
width: 28px;
height: 28px;
border-radius: 9px;
background: conic-gradient(from 250deg,var(--accent),oklch(0.6 0.14 220));
}
.sbl-29__name {
font: 700 clamp(13px,6cqi,16px)/1 system-ui,sans-serif;
white-space: nowrap;
}
.sbl-29__nav {
display: flex;
flex-direction: column;
gap: 3px;
}
.sbl-29__nav a {
display: flex;
align-items: center;
gap: 11px;
min-height: 44px;
padding: 0 12px;
border-radius: 11px;
font: 500 14px/1 system-ui,sans-serif;
color: var(--mut);
text-decoration: none;
white-space: nowrap;
transition: background-color .18s ease,color .18s ease;
}
.sbl-29__nav svg {
flex: none;
width: 19px;
height: 19px;
fill: none;
stroke: currentColor;
stroke-width: 1.8;
stroke-linecap: round;
stroke-linejoin: round;
}
.sbl-29__nav a:hover {
color: var(--ink);
background: color-mix(in oklab,var(--ink) 6%,transparent);
}
.sbl-29__nav a[aria-current="page"] {
color: var(--accent);
background: color-mix(in oklab,var(--accent) 14%,transparent);
}
.sbl-29__nav a:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
.sbl-29__meta {
margin-left: auto;
font: 600 11px/1 system-ui,sans-serif;
color: var(--mut);
background: color-mix(in oklab,var(--ink) 8%,transparent);
border-radius: 999px;
padding: 3px 8px;
}
@container sbl29 (max-width: 220px) {
.sbl-29__meta {
display: none;
}
}
@container sbl29 (max-width: 120px) {
.sbl-29__lbl,
.sbl-29__name {
display: none;
}
.sbl-29__nav a {
justify-content: center;
padding: 0;
width: 44px;
margin-inline: auto;
}
.sbl-29__brand {
justify-content: center;
padding-inline: 0;
}
}
.sbl-29__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;
}
.sbl-29__grip:hover {
opacity: 1;
}
.sbl-29__grip i {
width: 3px;
height: 3px;
border-radius: 50%;
background: var(--mut);
}
.sbl-29 > .sbl-29__frame + .sbl-29__main,
.sbl-29__main {
flex: 1;
min-width: 0;
padding: 32px;
overflow-y: auto;
}
.sbl-29__main h1 {
font: 600 22px/1.2 system-ui,sans-serif;
margin-bottom: 10px;
}
.sbl-29__main p {
color: var(--mut);
font-size: 14px;
line-height: 1.65;
max-width: 50ch;
text-wrap: pretty;
}
@media (prefers-reduced-motion: reduce) {
.sbl-29__nav a {
transition: none;
}
}/* No JavaScript — container:sbl29/inline-size makes the rail's own width queryable; @container rules morph it through three densities as you drag. */
/* No JavaScript — container:sbl29/inline-size makes the rail's own width queryable; @container rules morph it through three densities as you drag. */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: Next-Gen Adaptive Sidebar Layout Using CSS Container Queries
Source: https://codefronts.com/layouts/css-sidebar-layouts/adaptive-container-query/
A sidebar that responds to ITS OWN width, not the viewport: drag the demo's resize handle and watch the nav morph through three densities — full labels+meta, labels only, icon rail — driven entirely by @container rules. Drop this component into any column of any app and it self-adapts; media queries could never know.
## HTML
```html
<section class="sbl-29">
<div class="sbl-29__frame">
<aside class="sbl-29__rail" aria-label="Library">
<div class="sbl-29__brand"><span class="sbl-29__mark" aria-hidden="true"></span><span class="sbl-29__name">Shelfspace</span></div>
<nav class="sbl-29__nav">
<a href="#" aria-current="page"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20V2H6.5A2.5 2.5 0 0 0 4 4.5zM4 19.5A2.5 2.5 0 0 0 6.5 22H20v-5"/></svg><span class="sbl-29__lbl">Reading now</span><small class="sbl-29__meta">3</small></a>
<a href="#"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6 2h12a2 2 0 0 1 2 2v18l-8-4-8 4V4a2 2 0 0 1 2-2z"/></svg><span class="sbl-29__lbl">Saved</span><small class="sbl-29__meta">47</small></a>
<a href="#"><svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3.5 2"/></svg><span class="sbl-29__lbl">History</span><small class="sbl-29__meta">210</small></a>
<a href="#"><svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="3"/><path d="M12 2v3m0 14v3M2 12h3m14 0h3M4.9 4.9l2.1 2.1m10 10 2.1 2.1m0-14.2-2.1 2.1m-10 10-2.1 2.1"/></svg><span class="sbl-29__lbl">Settings</span></a>
</nav>
</aside>
<span class="sbl-29__grip" aria-hidden="true"><i></i><i></i><i></i></span>
</div>
<main class="sbl-29__main"><h1>Reading now</h1><p>Drag the dotted edge. Full → labels-only → icon rail: three densities from @container rules — the sidebar reads its OWN width, never the viewport's.</p></main>
</section>
```
## CSS
```css
.sbl-29,
.sbl-29 *,
.sbl-29 *::before,
.sbl-29 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.sbl-29 {
--panel: oklch(0.17 0.016 155);
--ink: oklch(0.94 0.005 155);
--mut: oklch(0.64 0.013 155);
--line: oklch(0.28 0.017 155);
--accent: oklch(0.78 0.15 160);
display: flex;
height: 100vh;
height: 100dvh;
font-family: system-ui,'Segoe UI',sans-serif;
background: oklch(0.13 0.012 155);
color: var(--ink);
}
.sbl-29__frame {
position: relative;
flex: none;
resize: horizontal;
overflow: auto;
width: 264px;
min-width: 76px;
max-width: 340px;
container: sbl29/inline-size;
background: var(--panel);
border-right: 1px solid var(--line);
}
.sbl-29__rail {
padding: 16px 10px;
min-height: 100%;
}
.sbl-29__brand {
display: flex;
align-items: center;
gap: 10px;
padding: 4px 10px 16px;
overflow: hidden;
}
.sbl-29__mark {
flex: none;
width: 28px;
height: 28px;
border-radius: 9px;
background: conic-gradient(from 250deg,var(--accent),oklch(0.6 0.14 220));
}
.sbl-29__name {
font: 700 clamp(13px,6cqi,16px)/1 system-ui,sans-serif;
white-space: nowrap;
}
.sbl-29__nav {
display: flex;
flex-direction: column;
gap: 3px;
}
.sbl-29__nav a {
display: flex;
align-items: center;
gap: 11px;
min-height: 44px;
padding: 0 12px;
border-radius: 11px;
font: 500 14px/1 system-ui,sans-serif;
color: var(--mut);
text-decoration: none;
white-space: nowrap;
transition: background-color .18s ease,color .18s ease;
}
.sbl-29__nav svg {
flex: none;
width: 19px;
height: 19px;
fill: none;
stroke: currentColor;
stroke-width: 1.8;
stroke-linecap: round;
stroke-linejoin: round;
}
.sbl-29__nav a:hover {
color: var(--ink);
background: color-mix(in oklab,var(--ink) 6%,transparent);
}
.sbl-29__nav a[aria-current="page"] {
color: var(--accent);
background: color-mix(in oklab,var(--accent) 14%,transparent);
}
.sbl-29__nav a:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
.sbl-29__meta {
margin-left: auto;
font: 600 11px/1 system-ui,sans-serif;
color: var(--mut);
background: color-mix(in oklab,var(--ink) 8%,transparent);
border-radius: 999px;
padding: 3px 8px;
}
@container sbl29 (max-width: 220px) {
.sbl-29__meta {
display: none;
}
}
@container sbl29 (max-width: 120px) {
.sbl-29__lbl,
.sbl-29__name {
display: none;
}
.sbl-29__nav a {
justify-content: center;
padding: 0;
width: 44px;
margin-inline: auto;
}
.sbl-29__brand {
justify-content: center;
padding-inline: 0;
}
}
.sbl-29__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;
}
.sbl-29__grip:hover {
opacity: 1;
}
.sbl-29__grip i {
width: 3px;
height: 3px;
border-radius: 50%;
background: var(--mut);
}
.sbl-29 > .sbl-29__frame + .sbl-29__main,
.sbl-29__main {
flex: 1;
min-width: 0;
padding: 32px;
overflow-y: auto;
}
.sbl-29__main h1 {
font: 600 22px/1.2 system-ui,sans-serif;
margin-bottom: 10px;
}
.sbl-29__main p {
color: var(--mut);
font-size: 14px;
line-height: 1.65;
max-width: 50ch;
text-wrap: pretty;
}
@media (prefers-reduced-motion: reduce) {
.sbl-29__nav a {
transition: none;
}
}
```
## JavaScript
```js
/* No JavaScript — container:sbl29/inline-size makes the rail's own width queryable; @container rules morph it through three densities as you drag. */
```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: Next-Gen Adaptive Sidebar Layout Using CSS Container Queries
Source: https://codefronts.com/layouts/css-sidebar-layouts/adaptive-container-query/
A sidebar that responds to ITS OWN width, not the viewport: drag the demo's resize handle and watch the nav morph through three densities — full labels+meta, labels only, icon rail — driven entirely by @container rules. Drop this component into any column of any app and it self-adapts; media queries could never know.
## HTML
```html
<section class="sbl-29">
<div class="sbl-29__frame">
<aside class="sbl-29__rail" aria-label="Library">
<div class="sbl-29__brand"><span class="sbl-29__mark" aria-hidden="true"></span><span class="sbl-29__name">Shelfspace</span></div>
<nav class="sbl-29__nav">
<a href="#" aria-current="page"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20V2H6.5A2.5 2.5 0 0 0 4 4.5zM4 19.5A2.5 2.5 0 0 0 6.5 22H20v-5"/></svg><span class="sbl-29__lbl">Reading now</span><small class="sbl-29__meta">3</small></a>
<a href="#"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6 2h12a2 2 0 0 1 2 2v18l-8-4-8 4V4a2 2 0 0 1 2-2z"/></svg><span class="sbl-29__lbl">Saved</span><small class="sbl-29__meta">47</small></a>
<a href="#"><svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3.5 2"/></svg><span class="sbl-29__lbl">History</span><small class="sbl-29__meta">210</small></a>
<a href="#"><svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="3"/><path d="M12 2v3m0 14v3M2 12h3m14 0h3M4.9 4.9l2.1 2.1m10 10 2.1 2.1m0-14.2-2.1 2.1m-10 10-2.1 2.1"/></svg><span class="sbl-29__lbl">Settings</span></a>
</nav>
</aside>
<span class="sbl-29__grip" aria-hidden="true"><i></i><i></i><i></i></span>
</div>
<main class="sbl-29__main"><h1>Reading now</h1><p>Drag the dotted edge. Full → labels-only → icon rail: three densities from @container rules — the sidebar reads its OWN width, never the viewport's.</p></main>
</section>
```
## CSS
```css
.sbl-29,
.sbl-29 *,
.sbl-29 *::before,
.sbl-29 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.sbl-29 {
--panel: oklch(0.17 0.016 155);
--ink: oklch(0.94 0.005 155);
--mut: oklch(0.64 0.013 155);
--line: oklch(0.28 0.017 155);
--accent: oklch(0.78 0.15 160);
display: flex;
height: 100vh;
height: 100dvh;
font-family: system-ui,'Segoe UI',sans-serif;
background: oklch(0.13 0.012 155);
color: var(--ink);
}
.sbl-29__frame {
position: relative;
flex: none;
resize: horizontal;
overflow: auto;
width: 264px;
min-width: 76px;
max-width: 340px;
container: sbl29/inline-size;
background: var(--panel);
border-right: 1px solid var(--line);
}
.sbl-29__rail {
padding: 16px 10px;
min-height: 100%;
}
.sbl-29__brand {
display: flex;
align-items: center;
gap: 10px;
padding: 4px 10px 16px;
overflow: hidden;
}
.sbl-29__mark {
flex: none;
width: 28px;
height: 28px;
border-radius: 9px;
background: conic-gradient(from 250deg,var(--accent),oklch(0.6 0.14 220));
}
.sbl-29__name {
font: 700 clamp(13px,6cqi,16px)/1 system-ui,sans-serif;
white-space: nowrap;
}
.sbl-29__nav {
display: flex;
flex-direction: column;
gap: 3px;
}
.sbl-29__nav a {
display: flex;
align-items: center;
gap: 11px;
min-height: 44px;
padding: 0 12px;
border-radius: 11px;
font: 500 14px/1 system-ui,sans-serif;
color: var(--mut);
text-decoration: none;
white-space: nowrap;
transition: background-color .18s ease,color .18s ease;
}
.sbl-29__nav svg {
flex: none;
width: 19px;
height: 19px;
fill: none;
stroke: currentColor;
stroke-width: 1.8;
stroke-linecap: round;
stroke-linejoin: round;
}
.sbl-29__nav a:hover {
color: var(--ink);
background: color-mix(in oklab,var(--ink) 6%,transparent);
}
.sbl-29__nav a[aria-current="page"] {
color: var(--accent);
background: color-mix(in oklab,var(--accent) 14%,transparent);
}
.sbl-29__nav a:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
.sbl-29__meta {
margin-left: auto;
font: 600 11px/1 system-ui,sans-serif;
color: var(--mut);
background: color-mix(in oklab,var(--ink) 8%,transparent);
border-radius: 999px;
padding: 3px 8px;
}
@container sbl29 (max-width: 220px) {
.sbl-29__meta {
display: none;
}
}
@container sbl29 (max-width: 120px) {
.sbl-29__lbl,
.sbl-29__name {
display: none;
}
.sbl-29__nav a {
justify-content: center;
padding: 0;
width: 44px;
margin-inline: auto;
}
.sbl-29__brand {
justify-content: center;
padding-inline: 0;
}
}
.sbl-29__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;
}
.sbl-29__grip:hover {
opacity: 1;
}
.sbl-29__grip i {
width: 3px;
height: 3px;
border-radius: 50%;
background: var(--mut);
}
.sbl-29 > .sbl-29__frame + .sbl-29__main,
.sbl-29__main {
flex: 1;
min-width: 0;
padding: 32px;
overflow-y: auto;
}
.sbl-29__main h1 {
font: 600 22px/1.2 system-ui,sans-serif;
margin-bottom: 10px;
}
.sbl-29__main p {
color: var(--mut);
font-size: 14px;
line-height: 1.65;
max-width: 50ch;
text-wrap: pretty;
}
@media (prefers-reduced-motion: reduce) {
.sbl-29__nav a {
transition: none;
}
}
```
## JavaScript
```js
/* No JavaScript — container:sbl29/inline-size makes the rail's own width queryable; @container rules morph it through three densities as you drag. */
```How this works
The wrapper declares itself a container: container:sbl29/inline-size. That one property makes the sidebar's own inline width queryable by every descendant. Three @container sbl29 (max-width:…) blocks then restyle internals: under 220px the meta line (item counts) hides; under 120px labels hide, links become centered 44px icon squares, and the brand collapses to its mark. The component carries its own responsive brain — the page it lands in needs zero knowledge of these breakpoints.
The demo makes the point tangible by wrapping the sidebar in demo 27's resize:horizontal container, so YOU are the layout engine: drag and watch the container queries fire. This is the architectural unlock of container queries — the same sidebar file serves the full-width app shell, a narrow split-view column, and a cramped embedded widget, adapting in each without props, JS, or viewport assumptions. Container units (cqi) scale the brand type with the rail for free.
Make it yours
- Density thresholds (220/120px) are the component's API — document them like props.
- Add a fourth ultra-wide tier (@container (min-width:320px)) that reveals recent-items sublists.
- cqi units scale anything with the container — try padding:calc(8px + 2cqi) for fluid density.
- In production the resize wrapper disappears — your app's real columns (grid tracks, split panes) become the containers.
Gotchas — read before shipping
- A container CANNOT size itself by its contents in the queried axis — inline-size containment makes width come from the layout, which is exactly why the wrapper (not the sidebar) carries the resize.
- Name your containers (@container sbl29) — unnamed queries match the NEAREST ancestor container, and a stray parent container silently hijacks the rules.
- Container queries style DESCENDANTS only; the container itself can't respond to its own query — style the inner aside, not the wrapper.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 114+ | 17.5+ | 121+ | 114+ |
Floor set by oklch(), color-mix(), @container, text-wrap as this demo is written. The core technique itself goes back further — Chrome 106+.
Size container queries are baseline since Feb 2023 — fully production-ready. cqi units ship in the same versions.