18 / 20
All 20 in 20 CSS Sidebar Navigation
- 01Accessible Sidebar Navigation ARIA Reference
- 02Sidebar Focus Trap and Restore on Close
- 03Inert Background Content Behind a Drawer
- 04Sidebar Navigation Is Not a Tab List
- 05Announcing Collapsed and Expanded Sidebar State
- 06Roving Tabindex for Sidebar Keyboard Navigation
- 07Persisting Sidebar Collapse State Across Reloads
- 08Sidebar Scroll Containment with Overscroll Behavior
- 09Nested Submenu Keyboard and Screen Reader Behavior
- 10Resizable Sidebar with a Keyboard Accessible Handle
- 11Collapsible Sidebar with Checkbox State
- 12Responsive Sidebar Breakpoint Behavior
- 13Icon Rail Sidebar with Accessible Labels
- 14Dashboard Sidebar with Current Page State
- 15Multi Level Sidebar Menu Disclosure Pattern
- 16Accordion Sidebar with Details and Summary
- 17Sticky Full Height Sidebar Layout Mechanics
- 18Floating Sidebar Stacking and Overlay Order
- 19Off Canvas Sidebar Drawer with Focus Return
- 20Sidebar Footer Pinning with Scrollable Nav
Floating Sidebar Stacking and Overlay Order
Why a floating sidebar with z-index: 9999 can still vanish behind a card. A transform, filter, opacity below 1 or will-change on an ancestor creates a stacking context, and z-index only orders siblings inside their own context — so the number is being compared against the wrong things. The panel and its backdrop are siblings in one deliberately isolated context, which is the arrangement that survives.
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="sdb-18" aria-label="Nova stage">
<div class="sdb-18__theme">
<input class="sdb-18__theme-input" type="checkbox" id="sdb-18-theme">
<label class="sdb-18__theme-label" for="sdb-18-theme">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="8.5" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M12 3.5a8.5 8.5 0 0 0 0 17Z" fill="currentColor"/></svg>
<span class="sdb-18__sr">Toggle dark theme</span>
</label>
</div>
<div class="sdb-18__scene">
<div class="sdb-18__content" id="sdb-18-lib">
<p class="sdb-18__scene-title">Continue watching</p>
<div class="sdb-18__cards">
<article class="sdb-18__card"><p>Harbour Wall</p><span>ep 4</span></article>
<article class="sdb-18__card"><p>Field Recordings</p><span>ep 11</span></article>
<article class="sdb-18__card"><p>Slack Water</p><span>ep 2</span></article>
<article class="sdb-18__card"><p>Kestrel</p><span>ep 7</span></article>
<article class="sdb-18__card"><p>Two Bells</p><span>ep 1</span></article>
<article class="sdb-18__card"><p>Low Tide</p><span>ep 9</span></article>
</div>
</div>
<div class="sdb-18__layer">
<div class="sdb-18__backdrop" aria-hidden="true"></div>
<nav class="sdb-18__panel" aria-label="Nova library">
<p class="sdb-18__panel-title">Library</p>
<ul class="sdb-18__list">
<li><a class="sdb-18__link" href="#sdb-18-lib" aria-current="page">Continue watching</a></li>
<li><a class="sdb-18__link" href="#sdb-18-lib">My list</a></li>
<li><a class="sdb-18__link" href="#sdb-18-lib">Downloads</a></li>
<li><a class="sdb-18__link" href="#sdb-18-lib">Channels</a></li>
<li><a class="sdb-18__link" href="#sdb-18-lib">Settings</a></li>
</ul>
</nav>
</div>
</div>
</section><section class="sdb-18" aria-label="Nova stage">
<div class="sdb-18__theme">
<input class="sdb-18__theme-input" type="checkbox" id="sdb-18-theme">
<label class="sdb-18__theme-label" for="sdb-18-theme">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="8.5" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M12 3.5a8.5 8.5 0 0 0 0 17Z" fill="currentColor"/></svg>
<span class="sdb-18__sr">Toggle dark theme</span>
</label>
</div>
<div class="sdb-18__scene">
<div class="sdb-18__content" id="sdb-18-lib">
<p class="sdb-18__scene-title">Continue watching</p>
<div class="sdb-18__cards">
<article class="sdb-18__card"><p>Harbour Wall</p><span>ep 4</span></article>
<article class="sdb-18__card"><p>Field Recordings</p><span>ep 11</span></article>
<article class="sdb-18__card"><p>Slack Water</p><span>ep 2</span></article>
<article class="sdb-18__card"><p>Kestrel</p><span>ep 7</span></article>
<article class="sdb-18__card"><p>Two Bells</p><span>ep 1</span></article>
<article class="sdb-18__card"><p>Low Tide</p><span>ep 9</span></article>
</div>
</div>
<div class="sdb-18__layer">
<div class="sdb-18__backdrop" aria-hidden="true"></div>
<nav class="sdb-18__panel" aria-label="Nova library">
<p class="sdb-18__panel-title">Library</p>
<ul class="sdb-18__list">
<li><a class="sdb-18__link" href="#sdb-18-lib" aria-current="page">Continue watching</a></li>
<li><a class="sdb-18__link" href="#sdb-18-lib">My list</a></li>
<li><a class="sdb-18__link" href="#sdb-18-lib">Downloads</a></li>
<li><a class="sdb-18__link" href="#sdb-18-lib">Channels</a></li>
<li><a class="sdb-18__link" href="#sdb-18-lib">Settings</a></li>
</ul>
</nav>
</div>
</div>
</section>.sdb-18 {
--sdb-18-bg: #070c1a;
--sdb-18-surf: #0e1730;
--sdb-18-glass: rgba(20, 32, 64, 0.62);
--sdb-18-line: #1f2c4d;
--sdb-18-ink: #e8ecfa;
--sdb-18-mut: #8e9bc0;
--sdb-18-acc: #6ff2d0;
--sdb-18-acc: oklch(88% 0.13 172);
--sdb-18-acc-soft: #10332f;
--sdb-18-font-display: "Helvetica Neue", Helvetica, Arial, system-ui, sans-serif;
width: 100%;
min-height: 100vh;
display: block;
position: relative;
box-sizing: border-box;
background: var(--sdb-18-bg);
color: var(--sdb-18-ink);
font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
line-height: 1.55;
}
.sdb-18 *,
.sdb-18 *::before,
.sdb-18 *::after {
box-sizing: border-box;
}
.sdb-18:has(.sdb-18__theme-input:checked) {
--sdb-18-bg: #eef1f8;
--sdb-18-surf: #ffffff;
--sdb-18-glass: rgba(255, 255, 255, 0.68);
--sdb-18-line: #d3d9e8;
--sdb-18-ink: #101527;
--sdb-18-mut: #565f7a;
--sdb-18-acc: #0a6f5c;
--sdb-18-acc: oklch(46% 0.1 172);
--sdb-18-acc-soft: #d3ece6;
}
@media (prefers-color-scheme: light) {
.sdb-18 {
--sdb-18-bg: #eef1f8;
--sdb-18-surf: #ffffff;
--sdb-18-glass: rgba(255, 255, 255, 0.68);
--sdb-18-line: #d3d9e8;
--sdb-18-ink: #101527;
--sdb-18-mut: #565f7a;
--sdb-18-acc: #0a6f5c;
--sdb-18-acc: oklch(46% 0.1 172);
--sdb-18-acc-soft: #d3ece6;
}
.sdb-18:has(.sdb-18__theme-input:checked) {
--sdb-18-bg: #070c1a;
--sdb-18-surf: #0e1730;
--sdb-18-glass: rgba(20, 32, 64, 0.62);
--sdb-18-line: #1f2c4d;
--sdb-18-ink: #e8ecfa;
--sdb-18-mut: #8e9bc0;
--sdb-18-acc: #6ff2d0;
--sdb-18-acc: oklch(88% 0.13 172);
--sdb-18-acc-soft: #10332f;
}
}
@media (prefers-color-scheme: dark) {
.sdb-18:has(.sdb-18__theme-input:checked) {
--sdb-18-bg: #eef1f8;
--sdb-18-surf: #ffffff;
--sdb-18-glass: rgba(255, 255, 255, 0.68);
--sdb-18-line: #d3d9e8;
--sdb-18-ink: #101527;
--sdb-18-mut: #565f7a;
--sdb-18-acc: #0a6f5c;
--sdb-18-acc: oklch(46% 0.1 172);
--sdb-18-acc-soft: #d3ece6;
}
}
.sdb-18__theme {
position: absolute;
inset-block-start: 0.85rem;
inset-inline-end: 0.85rem;
z-index: 90;
}
.sdb-18__theme-input {
position: absolute;
inset: 0;
inline-size: 100%;
block-size: 100%;
margin: 0;
opacity: 0;
cursor: pointer;
}
.sdb-18__theme-label {
display: grid;
place-items: center;
inline-size: 2.75rem;
block-size: 2.75rem;
border-radius: 0.9rem;
border: 1px solid var(--sdb-18-line);
background: var(--sdb-18-surf);
color: var(--sdb-18-ink);
cursor: pointer;
}
.sdb-18__theme-label svg {
inline-size: 1.15rem;
block-size: 1.15rem;
}
.sdb-18__theme-input:focus-visible + .sdb-18__theme-label {
outline: 2px solid var(--sdb-18-acc);
outline-offset: 2px;
}
.sdb-18__sr {
position: absolute;
inline-size: 1px;
block-size: 1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
}
/* isolation: isolate declares this subtree as one stacking unit deliberately,
rather than getting a context by accident from a transform. */
.sdb-18__scene {
position: relative;
isolation: isolate;
overflow: hidden;
min-block-size: 100vh;
background: var(--sdb-18-surf);
padding: 1.25rem;
}
@media (min-width: 46rem) {
.sdb-18__scene {
padding: 2rem;
}
}
.sdb-18__content {
min-inline-size: 0;
padding-inline-end: 4rem;
}
@media (min-width: 46rem) {
.sdb-18__content {
padding-inline-end: 17rem;
}
}
.sdb-18__scene-title {
margin: 0 0 0.9rem;
font-family: var(--sdb-18-font-display);
font-size: 0.78rem;
letter-spacing: 0.16em;
text-transform: uppercase;
color: var(--sdb-18-mut);
}
.sdb-18__cards {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(8.5rem, 1fr));
gap: 0.7rem;
}
.sdb-18__card {
min-inline-size: 0;
aspect-ratio: 3 / 4;
padding: 0.7rem;
display: flex;
flex-direction: column;
justify-content: flex-end;
gap: 0.15rem;
border-radius: 0.8rem;
background: var(--sdb-18-bg);
border: 1px solid var(--sdb-18-line);
}
.sdb-18__card p {
margin: 0;
font-size: 0.88rem;
font-weight: 600;
min-inline-size: 0;
}
.sdb-18__card span {
font-size: 0.74rem;
color: var(--sdb-18-mut);
}
.sdb-18__card:nth-child(2) {
border-color: var(--sdb-18-acc);
}
.sdb-18__layer {
position: absolute;
inset: 0;
z-index: 20;
pointer-events: none;
}
.sdb-18__backdrop {
position: absolute;
inset: 0;
z-index: 30;
background: rgba(4, 8, 20, 0.28);
}
.sdb-18__panel {
position: absolute;
z-index: 40;
inset-block: 1.5rem;
inset-inline-end: 1.5rem;
inline-size: min(14.5rem, 62%);
pointer-events: auto;
display: flex;
flex-direction: column;
padding: 0.8rem;
border-radius: 1rem;
border: 1px solid var(--sdb-18-line);
background: var(--sdb-18-glass);
-webkit-backdrop-filter: blur(14px) saturate(140%);
backdrop-filter: blur(14px) saturate(140%);
box-shadow: 0 1.2rem 2.6rem rgba(3, 6, 16, 0.45);
}
.sdb-18__panel-title {
margin: 0 0 0.6rem;
font-family: var(--sdb-18-font-display);
font-size: 0.7rem;
letter-spacing: 0.18em;
text-transform: uppercase;
color: var(--sdb-18-mut);
}
.sdb-18__list {
list-style: none;
margin: 0;
padding: 0;
display: grid;
gap: 0.15rem;
}
.sdb-18__link {
display: flex;
align-items: center;
min-block-size: 2.75rem;
padding: 0.45rem 0.6rem;
border-radius: 0.7rem;
color: var(--sdb-18-ink);
text-decoration: none;
font-size: 0.88rem;
min-inline-size: 0;
}
.sdb-18__link:hover {
background: var(--sdb-18-acc-soft);
}
.sdb-18__link[aria-current="page"] {
background: var(--sdb-18-acc);
color: var(--sdb-18-bg);
font-weight: 650;
}
.sdb-18__link:focus-visible {
outline: 2px solid var(--sdb-18-acc);
outline-offset: 2px;
}
@media (max-width: 32rem) {
.sdb-18__layer {
position: static;
}
.sdb-18__backdrop {
display: none;
}
.sdb-18__panel {
position: static;
inline-size: 100%;
margin-block-start: 1rem;
}
.sdb-18__content {
padding-inline-end: 0;
}
}.sdb-18 {
--sdb-18-bg: #070c1a;
--sdb-18-surf: #0e1730;
--sdb-18-glass: rgba(20, 32, 64, 0.62);
--sdb-18-line: #1f2c4d;
--sdb-18-ink: #e8ecfa;
--sdb-18-mut: #8e9bc0;
--sdb-18-acc: #6ff2d0;
--sdb-18-acc: oklch(88% 0.13 172);
--sdb-18-acc-soft: #10332f;
--sdb-18-font-display: "Helvetica Neue", Helvetica, Arial, system-ui, sans-serif;
width: 100%;
min-height: 100vh;
display: block;
position: relative;
box-sizing: border-box;
background: var(--sdb-18-bg);
color: var(--sdb-18-ink);
font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
line-height: 1.55;
}
.sdb-18 *,
.sdb-18 *::before,
.sdb-18 *::after {
box-sizing: border-box;
}
.sdb-18:has(.sdb-18__theme-input:checked) {
--sdb-18-bg: #eef1f8;
--sdb-18-surf: #ffffff;
--sdb-18-glass: rgba(255, 255, 255, 0.68);
--sdb-18-line: #d3d9e8;
--sdb-18-ink: #101527;
--sdb-18-mut: #565f7a;
--sdb-18-acc: #0a6f5c;
--sdb-18-acc: oklch(46% 0.1 172);
--sdb-18-acc-soft: #d3ece6;
}
@media (prefers-color-scheme: light) {
.sdb-18 {
--sdb-18-bg: #eef1f8;
--sdb-18-surf: #ffffff;
--sdb-18-glass: rgba(255, 255, 255, 0.68);
--sdb-18-line: #d3d9e8;
--sdb-18-ink: #101527;
--sdb-18-mut: #565f7a;
--sdb-18-acc: #0a6f5c;
--sdb-18-acc: oklch(46% 0.1 172);
--sdb-18-acc-soft: #d3ece6;
}
.sdb-18:has(.sdb-18__theme-input:checked) {
--sdb-18-bg: #070c1a;
--sdb-18-surf: #0e1730;
--sdb-18-glass: rgba(20, 32, 64, 0.62);
--sdb-18-line: #1f2c4d;
--sdb-18-ink: #e8ecfa;
--sdb-18-mut: #8e9bc0;
--sdb-18-acc: #6ff2d0;
--sdb-18-acc: oklch(88% 0.13 172);
--sdb-18-acc-soft: #10332f;
}
}
@media (prefers-color-scheme: dark) {
.sdb-18:has(.sdb-18__theme-input:checked) {
--sdb-18-bg: #eef1f8;
--sdb-18-surf: #ffffff;
--sdb-18-glass: rgba(255, 255, 255, 0.68);
--sdb-18-line: #d3d9e8;
--sdb-18-ink: #101527;
--sdb-18-mut: #565f7a;
--sdb-18-acc: #0a6f5c;
--sdb-18-acc: oklch(46% 0.1 172);
--sdb-18-acc-soft: #d3ece6;
}
}
.sdb-18__theme {
position: absolute;
inset-block-start: 0.85rem;
inset-inline-end: 0.85rem;
z-index: 90;
}
.sdb-18__theme-input {
position: absolute;
inset: 0;
inline-size: 100%;
block-size: 100%;
margin: 0;
opacity: 0;
cursor: pointer;
}
.sdb-18__theme-label {
display: grid;
place-items: center;
inline-size: 2.75rem;
block-size: 2.75rem;
border-radius: 0.9rem;
border: 1px solid var(--sdb-18-line);
background: var(--sdb-18-surf);
color: var(--sdb-18-ink);
cursor: pointer;
}
.sdb-18__theme-label svg {
inline-size: 1.15rem;
block-size: 1.15rem;
}
.sdb-18__theme-input:focus-visible + .sdb-18__theme-label {
outline: 2px solid var(--sdb-18-acc);
outline-offset: 2px;
}
.sdb-18__sr {
position: absolute;
inline-size: 1px;
block-size: 1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
}
/* isolation: isolate declares this subtree as one stacking unit deliberately,
rather than getting a context by accident from a transform. */
.sdb-18__scene {
position: relative;
isolation: isolate;
overflow: hidden;
min-block-size: 100vh;
background: var(--sdb-18-surf);
padding: 1.25rem;
}
@media (min-width: 46rem) {
.sdb-18__scene {
padding: 2rem;
}
}
.sdb-18__content {
min-inline-size: 0;
padding-inline-end: 4rem;
}
@media (min-width: 46rem) {
.sdb-18__content {
padding-inline-end: 17rem;
}
}
.sdb-18__scene-title {
margin: 0 0 0.9rem;
font-family: var(--sdb-18-font-display);
font-size: 0.78rem;
letter-spacing: 0.16em;
text-transform: uppercase;
color: var(--sdb-18-mut);
}
.sdb-18__cards {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(8.5rem, 1fr));
gap: 0.7rem;
}
.sdb-18__card {
min-inline-size: 0;
aspect-ratio: 3 / 4;
padding: 0.7rem;
display: flex;
flex-direction: column;
justify-content: flex-end;
gap: 0.15rem;
border-radius: 0.8rem;
background: var(--sdb-18-bg);
border: 1px solid var(--sdb-18-line);
}
.sdb-18__card p {
margin: 0;
font-size: 0.88rem;
font-weight: 600;
min-inline-size: 0;
}
.sdb-18__card span {
font-size: 0.74rem;
color: var(--sdb-18-mut);
}
.sdb-18__card:nth-child(2) {
border-color: var(--sdb-18-acc);
}
.sdb-18__layer {
position: absolute;
inset: 0;
z-index: 20;
pointer-events: none;
}
.sdb-18__backdrop {
position: absolute;
inset: 0;
z-index: 30;
background: rgba(4, 8, 20, 0.28);
}
.sdb-18__panel {
position: absolute;
z-index: 40;
inset-block: 1.5rem;
inset-inline-end: 1.5rem;
inline-size: min(14.5rem, 62%);
pointer-events: auto;
display: flex;
flex-direction: column;
padding: 0.8rem;
border-radius: 1rem;
border: 1px solid var(--sdb-18-line);
background: var(--sdb-18-glass);
-webkit-backdrop-filter: blur(14px) saturate(140%);
backdrop-filter: blur(14px) saturate(140%);
box-shadow: 0 1.2rem 2.6rem rgba(3, 6, 16, 0.45);
}
.sdb-18__panel-title {
margin: 0 0 0.6rem;
font-family: var(--sdb-18-font-display);
font-size: 0.7rem;
letter-spacing: 0.18em;
text-transform: uppercase;
color: var(--sdb-18-mut);
}
.sdb-18__list {
list-style: none;
margin: 0;
padding: 0;
display: grid;
gap: 0.15rem;
}
.sdb-18__link {
display: flex;
align-items: center;
min-block-size: 2.75rem;
padding: 0.45rem 0.6rem;
border-radius: 0.7rem;
color: var(--sdb-18-ink);
text-decoration: none;
font-size: 0.88rem;
min-inline-size: 0;
}
.sdb-18__link:hover {
background: var(--sdb-18-acc-soft);
}
.sdb-18__link[aria-current="page"] {
background: var(--sdb-18-acc);
color: var(--sdb-18-bg);
font-weight: 650;
}
.sdb-18__link:focus-visible {
outline: 2px solid var(--sdb-18-acc);
outline-offset: 2px;
}
@media (max-width: 32rem) {
.sdb-18__layer {
position: static;
}
.sdb-18__backdrop {
display: none;
}
.sdb-18__panel {
position: static;
inline-size: 100%;
margin-block-start: 1rem;
}
.sdb-18__content {
padding-inline-end: 0;
}
}Here's a working CSS Sidebar Navigation 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: Floating Sidebar Stacking and Overlay Order
Source: https://codefronts.com/navigation/css-sidebar-navigation/modern-glassmorphism-floating-sidebar-design-in-css/
Why a floating sidebar with z-index: 9999 can still vanish behind a card. A transform, filter, opacity below 1 or will-change on an ancestor creates a stacking context, and z-index only orders siblings inside their own context — so the number is being compared against the wrong things. The panel and its backdrop are siblings in one deliberately isolated context, which is the arrangement that survives.
## HTML
```html
<section class="sdb-18" aria-label="Nova stage">
<div class="sdb-18__theme">
<input class="sdb-18__theme-input" type="checkbox" id="sdb-18-theme">
<label class="sdb-18__theme-label" for="sdb-18-theme">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="8.5" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M12 3.5a8.5 8.5 0 0 0 0 17Z" fill="currentColor"/></svg>
<span class="sdb-18__sr">Toggle dark theme</span>
</label>
</div>
<div class="sdb-18__scene">
<div class="sdb-18__content" id="sdb-18-lib">
<p class="sdb-18__scene-title">Continue watching</p>
<div class="sdb-18__cards">
<article class="sdb-18__card"><p>Harbour Wall</p><span>ep 4</span></article>
<article class="sdb-18__card"><p>Field Recordings</p><span>ep 11</span></article>
<article class="sdb-18__card"><p>Slack Water</p><span>ep 2</span></article>
<article class="sdb-18__card"><p>Kestrel</p><span>ep 7</span></article>
<article class="sdb-18__card"><p>Two Bells</p><span>ep 1</span></article>
<article class="sdb-18__card"><p>Low Tide</p><span>ep 9</span></article>
</div>
</div>
<div class="sdb-18__layer">
<div class="sdb-18__backdrop" aria-hidden="true"></div>
<nav class="sdb-18__panel" aria-label="Nova library">
<p class="sdb-18__panel-title">Library</p>
<ul class="sdb-18__list">
<li><a class="sdb-18__link" href="#sdb-18-lib" aria-current="page">Continue watching</a></li>
<li><a class="sdb-18__link" href="#sdb-18-lib">My list</a></li>
<li><a class="sdb-18__link" href="#sdb-18-lib">Downloads</a></li>
<li><a class="sdb-18__link" href="#sdb-18-lib">Channels</a></li>
<li><a class="sdb-18__link" href="#sdb-18-lib">Settings</a></li>
</ul>
</nav>
</div>
</div>
</section>
```
## CSS
```css
.sdb-18 {
--sdb-18-bg: #070c1a;
--sdb-18-surf: #0e1730;
--sdb-18-glass: rgba(20, 32, 64, 0.62);
--sdb-18-line: #1f2c4d;
--sdb-18-ink: #e8ecfa;
--sdb-18-mut: #8e9bc0;
--sdb-18-acc: #6ff2d0;
--sdb-18-acc: oklch(88% 0.13 172);
--sdb-18-acc-soft: #10332f;
--sdb-18-font-display: "Helvetica Neue", Helvetica, Arial, system-ui, sans-serif;
width: 100%;
min-height: 100vh;
display: block;
position: relative;
box-sizing: border-box;
background: var(--sdb-18-bg);
color: var(--sdb-18-ink);
font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
line-height: 1.55;
}
.sdb-18 *,
.sdb-18 *::before,
.sdb-18 *::after {
box-sizing: border-box;
}
.sdb-18:has(.sdb-18__theme-input:checked) {
--sdb-18-bg: #eef1f8;
--sdb-18-surf: #ffffff;
--sdb-18-glass: rgba(255, 255, 255, 0.68);
--sdb-18-line: #d3d9e8;
--sdb-18-ink: #101527;
--sdb-18-mut: #565f7a;
--sdb-18-acc: #0a6f5c;
--sdb-18-acc: oklch(46% 0.1 172);
--sdb-18-acc-soft: #d3ece6;
}
@media (prefers-color-scheme: light) {
.sdb-18 {
--sdb-18-bg: #eef1f8;
--sdb-18-surf: #ffffff;
--sdb-18-glass: rgba(255, 255, 255, 0.68);
--sdb-18-line: #d3d9e8;
--sdb-18-ink: #101527;
--sdb-18-mut: #565f7a;
--sdb-18-acc: #0a6f5c;
--sdb-18-acc: oklch(46% 0.1 172);
--sdb-18-acc-soft: #d3ece6;
}
.sdb-18:has(.sdb-18__theme-input:checked) {
--sdb-18-bg: #070c1a;
--sdb-18-surf: #0e1730;
--sdb-18-glass: rgba(20, 32, 64, 0.62);
--sdb-18-line: #1f2c4d;
--sdb-18-ink: #e8ecfa;
--sdb-18-mut: #8e9bc0;
--sdb-18-acc: #6ff2d0;
--sdb-18-acc: oklch(88% 0.13 172);
--sdb-18-acc-soft: #10332f;
}
}
@media (prefers-color-scheme: dark) {
.sdb-18:has(.sdb-18__theme-input:checked) {
--sdb-18-bg: #eef1f8;
--sdb-18-surf: #ffffff;
--sdb-18-glass: rgba(255, 255, 255, 0.68);
--sdb-18-line: #d3d9e8;
--sdb-18-ink: #101527;
--sdb-18-mut: #565f7a;
--sdb-18-acc: #0a6f5c;
--sdb-18-acc: oklch(46% 0.1 172);
--sdb-18-acc-soft: #d3ece6;
}
}
.sdb-18__theme {
position: absolute;
inset-block-start: 0.85rem;
inset-inline-end: 0.85rem;
z-index: 90;
}
.sdb-18__theme-input {
position: absolute;
inset: 0;
inline-size: 100%;
block-size: 100%;
margin: 0;
opacity: 0;
cursor: pointer;
}
.sdb-18__theme-label {
display: grid;
place-items: center;
inline-size: 2.75rem;
block-size: 2.75rem;
border-radius: 0.9rem;
border: 1px solid var(--sdb-18-line);
background: var(--sdb-18-surf);
color: var(--sdb-18-ink);
cursor: pointer;
}
.sdb-18__theme-label svg {
inline-size: 1.15rem;
block-size: 1.15rem;
}
.sdb-18__theme-input:focus-visible + .sdb-18__theme-label {
outline: 2px solid var(--sdb-18-acc);
outline-offset: 2px;
}
.sdb-18__sr {
position: absolute;
inline-size: 1px;
block-size: 1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
}
/* isolation: isolate declares this subtree as one stacking unit deliberately,
rather than getting a context by accident from a transform. */
.sdb-18__scene {
position: relative;
isolation: isolate;
overflow: hidden;
min-block-size: 100vh;
background: var(--sdb-18-surf);
padding: 1.25rem;
}
@media (min-width: 46rem) {
.sdb-18__scene {
padding: 2rem;
}
}
.sdb-18__content {
min-inline-size: 0;
padding-inline-end: 4rem;
}
@media (min-width: 46rem) {
.sdb-18__content {
padding-inline-end: 17rem;
}
}
.sdb-18__scene-title {
margin: 0 0 0.9rem;
font-family: var(--sdb-18-font-display);
font-size: 0.78rem;
letter-spacing: 0.16em;
text-transform: uppercase;
color: var(--sdb-18-mut);
}
.sdb-18__cards {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(8.5rem, 1fr));
gap: 0.7rem;
}
.sdb-18__card {
min-inline-size: 0;
aspect-ratio: 3 / 4;
padding: 0.7rem;
display: flex;
flex-direction: column;
justify-content: flex-end;
gap: 0.15rem;
border-radius: 0.8rem;
background: var(--sdb-18-bg);
border: 1px solid var(--sdb-18-line);
}
.sdb-18__card p {
margin: 0;
font-size: 0.88rem;
font-weight: 600;
min-inline-size: 0;
}
.sdb-18__card span {
font-size: 0.74rem;
color: var(--sdb-18-mut);
}
.sdb-18__card:nth-child(2) {
border-color: var(--sdb-18-acc);
}
.sdb-18__layer {
position: absolute;
inset: 0;
z-index: 20;
pointer-events: none;
}
.sdb-18__backdrop {
position: absolute;
inset: 0;
z-index: 30;
background: rgba(4, 8, 20, 0.28);
}
.sdb-18__panel {
position: absolute;
z-index: 40;
inset-block: 1.5rem;
inset-inline-end: 1.5rem;
inline-size: min(14.5rem, 62%);
pointer-events: auto;
display: flex;
flex-direction: column;
padding: 0.8rem;
border-radius: 1rem;
border: 1px solid var(--sdb-18-line);
background: var(--sdb-18-glass);
-webkit-backdrop-filter: blur(14px) saturate(140%);
backdrop-filter: blur(14px) saturate(140%);
box-shadow: 0 1.2rem 2.6rem rgba(3, 6, 16, 0.45);
}
.sdb-18__panel-title {
margin: 0 0 0.6rem;
font-family: var(--sdb-18-font-display);
font-size: 0.7rem;
letter-spacing: 0.18em;
text-transform: uppercase;
color: var(--sdb-18-mut);
}
.sdb-18__list {
list-style: none;
margin: 0;
padding: 0;
display: grid;
gap: 0.15rem;
}
.sdb-18__link {
display: flex;
align-items: center;
min-block-size: 2.75rem;
padding: 0.45rem 0.6rem;
border-radius: 0.7rem;
color: var(--sdb-18-ink);
text-decoration: none;
font-size: 0.88rem;
min-inline-size: 0;
}
.sdb-18__link:hover {
background: var(--sdb-18-acc-soft);
}
.sdb-18__link[aria-current="page"] {
background: var(--sdb-18-acc);
color: var(--sdb-18-bg);
font-weight: 650;
}
.sdb-18__link:focus-visible {
outline: 2px solid var(--sdb-18-acc);
outline-offset: 2px;
}
@media (max-width: 32rem) {
.sdb-18__layer {
position: static;
}
.sdb-18__backdrop {
display: none;
}
.sdb-18__panel {
position: static;
inline-size: 100%;
margin-block-start: 1rem;
}
.sdb-18__content {
padding-inline-end: 0;
}
}
```Here's a working CSS Sidebar Navigation 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: Floating Sidebar Stacking and Overlay Order
Source: https://codefronts.com/navigation/css-sidebar-navigation/modern-glassmorphism-floating-sidebar-design-in-css/
Why a floating sidebar with z-index: 9999 can still vanish behind a card. A transform, filter, opacity below 1 or will-change on an ancestor creates a stacking context, and z-index only orders siblings inside their own context — so the number is being compared against the wrong things. The panel and its backdrop are siblings in one deliberately isolated context, which is the arrangement that survives.
## HTML
```html
<section class="sdb-18" aria-label="Nova stage">
<div class="sdb-18__theme">
<input class="sdb-18__theme-input" type="checkbox" id="sdb-18-theme">
<label class="sdb-18__theme-label" for="sdb-18-theme">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="8.5" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M12 3.5a8.5 8.5 0 0 0 0 17Z" fill="currentColor"/></svg>
<span class="sdb-18__sr">Toggle dark theme</span>
</label>
</div>
<div class="sdb-18__scene">
<div class="sdb-18__content" id="sdb-18-lib">
<p class="sdb-18__scene-title">Continue watching</p>
<div class="sdb-18__cards">
<article class="sdb-18__card"><p>Harbour Wall</p><span>ep 4</span></article>
<article class="sdb-18__card"><p>Field Recordings</p><span>ep 11</span></article>
<article class="sdb-18__card"><p>Slack Water</p><span>ep 2</span></article>
<article class="sdb-18__card"><p>Kestrel</p><span>ep 7</span></article>
<article class="sdb-18__card"><p>Two Bells</p><span>ep 1</span></article>
<article class="sdb-18__card"><p>Low Tide</p><span>ep 9</span></article>
</div>
</div>
<div class="sdb-18__layer">
<div class="sdb-18__backdrop" aria-hidden="true"></div>
<nav class="sdb-18__panel" aria-label="Nova library">
<p class="sdb-18__panel-title">Library</p>
<ul class="sdb-18__list">
<li><a class="sdb-18__link" href="#sdb-18-lib" aria-current="page">Continue watching</a></li>
<li><a class="sdb-18__link" href="#sdb-18-lib">My list</a></li>
<li><a class="sdb-18__link" href="#sdb-18-lib">Downloads</a></li>
<li><a class="sdb-18__link" href="#sdb-18-lib">Channels</a></li>
<li><a class="sdb-18__link" href="#sdb-18-lib">Settings</a></li>
</ul>
</nav>
</div>
</div>
</section>
```
## CSS
```css
.sdb-18 {
--sdb-18-bg: #070c1a;
--sdb-18-surf: #0e1730;
--sdb-18-glass: rgba(20, 32, 64, 0.62);
--sdb-18-line: #1f2c4d;
--sdb-18-ink: #e8ecfa;
--sdb-18-mut: #8e9bc0;
--sdb-18-acc: #6ff2d0;
--sdb-18-acc: oklch(88% 0.13 172);
--sdb-18-acc-soft: #10332f;
--sdb-18-font-display: "Helvetica Neue", Helvetica, Arial, system-ui, sans-serif;
width: 100%;
min-height: 100vh;
display: block;
position: relative;
box-sizing: border-box;
background: var(--sdb-18-bg);
color: var(--sdb-18-ink);
font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
line-height: 1.55;
}
.sdb-18 *,
.sdb-18 *::before,
.sdb-18 *::after {
box-sizing: border-box;
}
.sdb-18:has(.sdb-18__theme-input:checked) {
--sdb-18-bg: #eef1f8;
--sdb-18-surf: #ffffff;
--sdb-18-glass: rgba(255, 255, 255, 0.68);
--sdb-18-line: #d3d9e8;
--sdb-18-ink: #101527;
--sdb-18-mut: #565f7a;
--sdb-18-acc: #0a6f5c;
--sdb-18-acc: oklch(46% 0.1 172);
--sdb-18-acc-soft: #d3ece6;
}
@media (prefers-color-scheme: light) {
.sdb-18 {
--sdb-18-bg: #eef1f8;
--sdb-18-surf: #ffffff;
--sdb-18-glass: rgba(255, 255, 255, 0.68);
--sdb-18-line: #d3d9e8;
--sdb-18-ink: #101527;
--sdb-18-mut: #565f7a;
--sdb-18-acc: #0a6f5c;
--sdb-18-acc: oklch(46% 0.1 172);
--sdb-18-acc-soft: #d3ece6;
}
.sdb-18:has(.sdb-18__theme-input:checked) {
--sdb-18-bg: #070c1a;
--sdb-18-surf: #0e1730;
--sdb-18-glass: rgba(20, 32, 64, 0.62);
--sdb-18-line: #1f2c4d;
--sdb-18-ink: #e8ecfa;
--sdb-18-mut: #8e9bc0;
--sdb-18-acc: #6ff2d0;
--sdb-18-acc: oklch(88% 0.13 172);
--sdb-18-acc-soft: #10332f;
}
}
@media (prefers-color-scheme: dark) {
.sdb-18:has(.sdb-18__theme-input:checked) {
--sdb-18-bg: #eef1f8;
--sdb-18-surf: #ffffff;
--sdb-18-glass: rgba(255, 255, 255, 0.68);
--sdb-18-line: #d3d9e8;
--sdb-18-ink: #101527;
--sdb-18-mut: #565f7a;
--sdb-18-acc: #0a6f5c;
--sdb-18-acc: oklch(46% 0.1 172);
--sdb-18-acc-soft: #d3ece6;
}
}
.sdb-18__theme {
position: absolute;
inset-block-start: 0.85rem;
inset-inline-end: 0.85rem;
z-index: 90;
}
.sdb-18__theme-input {
position: absolute;
inset: 0;
inline-size: 100%;
block-size: 100%;
margin: 0;
opacity: 0;
cursor: pointer;
}
.sdb-18__theme-label {
display: grid;
place-items: center;
inline-size: 2.75rem;
block-size: 2.75rem;
border-radius: 0.9rem;
border: 1px solid var(--sdb-18-line);
background: var(--sdb-18-surf);
color: var(--sdb-18-ink);
cursor: pointer;
}
.sdb-18__theme-label svg {
inline-size: 1.15rem;
block-size: 1.15rem;
}
.sdb-18__theme-input:focus-visible + .sdb-18__theme-label {
outline: 2px solid var(--sdb-18-acc);
outline-offset: 2px;
}
.sdb-18__sr {
position: absolute;
inline-size: 1px;
block-size: 1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
}
/* isolation: isolate declares this subtree as one stacking unit deliberately,
rather than getting a context by accident from a transform. */
.sdb-18__scene {
position: relative;
isolation: isolate;
overflow: hidden;
min-block-size: 100vh;
background: var(--sdb-18-surf);
padding: 1.25rem;
}
@media (min-width: 46rem) {
.sdb-18__scene {
padding: 2rem;
}
}
.sdb-18__content {
min-inline-size: 0;
padding-inline-end: 4rem;
}
@media (min-width: 46rem) {
.sdb-18__content {
padding-inline-end: 17rem;
}
}
.sdb-18__scene-title {
margin: 0 0 0.9rem;
font-family: var(--sdb-18-font-display);
font-size: 0.78rem;
letter-spacing: 0.16em;
text-transform: uppercase;
color: var(--sdb-18-mut);
}
.sdb-18__cards {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(8.5rem, 1fr));
gap: 0.7rem;
}
.sdb-18__card {
min-inline-size: 0;
aspect-ratio: 3 / 4;
padding: 0.7rem;
display: flex;
flex-direction: column;
justify-content: flex-end;
gap: 0.15rem;
border-radius: 0.8rem;
background: var(--sdb-18-bg);
border: 1px solid var(--sdb-18-line);
}
.sdb-18__card p {
margin: 0;
font-size: 0.88rem;
font-weight: 600;
min-inline-size: 0;
}
.sdb-18__card span {
font-size: 0.74rem;
color: var(--sdb-18-mut);
}
.sdb-18__card:nth-child(2) {
border-color: var(--sdb-18-acc);
}
.sdb-18__layer {
position: absolute;
inset: 0;
z-index: 20;
pointer-events: none;
}
.sdb-18__backdrop {
position: absolute;
inset: 0;
z-index: 30;
background: rgba(4, 8, 20, 0.28);
}
.sdb-18__panel {
position: absolute;
z-index: 40;
inset-block: 1.5rem;
inset-inline-end: 1.5rem;
inline-size: min(14.5rem, 62%);
pointer-events: auto;
display: flex;
flex-direction: column;
padding: 0.8rem;
border-radius: 1rem;
border: 1px solid var(--sdb-18-line);
background: var(--sdb-18-glass);
-webkit-backdrop-filter: blur(14px) saturate(140%);
backdrop-filter: blur(14px) saturate(140%);
box-shadow: 0 1.2rem 2.6rem rgba(3, 6, 16, 0.45);
}
.sdb-18__panel-title {
margin: 0 0 0.6rem;
font-family: var(--sdb-18-font-display);
font-size: 0.7rem;
letter-spacing: 0.18em;
text-transform: uppercase;
color: var(--sdb-18-mut);
}
.sdb-18__list {
list-style: none;
margin: 0;
padding: 0;
display: grid;
gap: 0.15rem;
}
.sdb-18__link {
display: flex;
align-items: center;
min-block-size: 2.75rem;
padding: 0.45rem 0.6rem;
border-radius: 0.7rem;
color: var(--sdb-18-ink);
text-decoration: none;
font-size: 0.88rem;
min-inline-size: 0;
}
.sdb-18__link:hover {
background: var(--sdb-18-acc-soft);
}
.sdb-18__link[aria-current="page"] {
background: var(--sdb-18-acc);
color: var(--sdb-18-bg);
font-weight: 650;
}
.sdb-18__link:focus-visible {
outline: 2px solid var(--sdb-18-acc);
outline-offset: 2px;
}
@media (max-width: 32rem) {
.sdb-18__layer {
position: static;
}
.sdb-18__backdrop {
display: none;
}
.sdb-18__panel {
position: static;
inline-size: 100%;
margin-block-start: 1rem;
}
.sdb-18__content {
padding-inline-end: 0;
}
}
```How this works
z-index is not global, and that is the whole bug. An element's z-index only orders it against siblings within its nearest stacking context. If an ancestor forms a context, everything inside it paints as one unit, and a child's 9999 is compared only with its context-mates — not with that card two containers over. The number is not being ignored; it is being applied in a scope you did not intend.
Plenty of ordinary declarations create a stacking context. transform, filter, backdrop-filter, perspective, opacity below 1, will-change naming any of them, contain: paint, isolation: isolate, and any positioned element with a z-index other than auto. This is why the bug so often appears with an animation: someone adds transform for a hover lift, and a floating panel elsewhere in that subtree quietly loses its ability to escape.
The fix is structural, not numerical. Raise the number and nothing changes. Instead, move the floating panel out of the offending context — typically to a portal near the end of <body> — or remove the ancestor's context-forming declaration. Once the panel and the backdrop are siblings in one context, ordering them is trivial: backdrop below, panel above, and neither needs a number above single digits.
The trap after that is the backdrop. Putting the scrim inside the panel means it can never cover content outside the panel's context; putting it in a different context from the panel means one of the two ends up on the wrong side of the content. Backdrop and panel belong together, and isolation: isolate on their shared parent is the honest way to say "this subtree is one stacking unit" without borrowing a transform to do it.
Make it yours
- Change the panel's edge by swapping
inset-inline-endforinset-inline-start; the stacking rules are identical on either side. - Tune the glass with the two
backdrop-filtervalues; keep the-webkit-prefixed line for Safari below 18. - Raise the opaque fallback background if contrast on the glass drops below 4.5:1 over your busiest content.
- Replace
isolation: isolatewith nothing at all if the panel is already a direct child of<body>— you only need it when you are deliberately scoping. - Add a blur to the backdrop instead of the panel for the opposite reading, where the page recedes rather than the panel advancing.
- Move the panel to a portal near the end of
<body>if an ancestor in your app already forms a stacking context.
Gotchas — read before shipping
- A
transformon an ancestor creates a stacking context, so a floating panel inside it cannot paint above anything outside it — regardless of how large itsz-indexis. will-change: transformcreates the context too, which makes this bug appear from a pure performance change with no visual intent.- A backdrop in a different stacking context from its panel always ends up on the wrong side of something. Keep them as siblings.
backdrop-filterneeds the-webkit-prefix for Safari below 18, and it also creates a stacking context itself — so the glass panel is both victim and cause.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 15.4+ | 121+ | 111+ |
backdrop-filter is the notable feature: Chrome 76, Safari 18 unprefixed with -webkit- back to 9, Firefox 103 — both forms ship here. oklch() and the :has() toggle set the stated floor. Engines without backdrop-filter get the opaque translucent background declared before it, so the panel stays legible; the stacking behaviour being taught has no version floor at all.