14 / 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
Dashboard Sidebar with Current Page State
aria-current="page" as the single source of truth for the active item, with every visual affordance derived from the attribute instead of a parallel class. One state, not two that can disagree: the row fill, the inset marker, the icon colour, the breadcrumb and the group's containment dot all read the same attribute.
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-14" aria-label="Meridian admin">
<div class="sdb-14__theme">
<input class="sdb-14__theme-input" type="checkbox" id="sdb-14-theme">
<label class="sdb-14__theme-label" for="sdb-14-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-14__sr">Toggle dark theme</span>
</label>
</div>
<div class="sdb-14__app">
<nav class="sdb-14__rail" aria-label="Meridian admin">
<p class="sdb-14__rail-title">Meridian</p>
<ul class="sdb-14__list">
<li><a class="sdb-14__link" href="#sdb-14-main"><span class="sdb-14__ico" aria-hidden="true">□</span>Overview</a></li>
</ul>
<details class="sdb-14__group" open>
<summary class="sdb-14__summary"><span class="sdb-14__ico" aria-hidden="true">▼</span>Infrastructure</summary>
<ul class="sdb-14__list sdb-14__list--sub">
<li><a class="sdb-14__link sdb-14__link--sub" href="#sdb-14-main">Regions</a></li>
<li><a class="sdb-14__link sdb-14__link--sub" href="#sdb-14-main" aria-current="page">Deployments</a></li>
<li><a class="sdb-14__link sdb-14__link--sub" href="#sdb-14-main">Certificates</a></li>
</ul>
</details>
<details class="sdb-14__group">
<summary class="sdb-14__summary"><span class="sdb-14__ico" aria-hidden="true">▼</span>Access</summary>
<ul class="sdb-14__list sdb-14__list--sub">
<li><a class="sdb-14__link sdb-14__link--sub" href="#sdb-14-main">Members</a></li>
<li><a class="sdb-14__link sdb-14__link--sub" href="#sdb-14-main">API keys</a></li>
<li><a class="sdb-14__link sdb-14__link--sub" href="#sdb-14-main">Audit log</a></li>
</ul>
</details>
<ul class="sdb-14__list">
<li><a class="sdb-14__link" href="#sdb-14-main"><span class="sdb-14__ico" aria-hidden="true">□</span>Billing</a></li>
</ul>
</nav>
<main class="sdb-14__main" id="sdb-14-main">
<p class="sdb-14__crumb">Infrastructure → <span class="sdb-14__crumb-here">Deployments</span></p>
<h2 class="sdb-14__main-h">Deployments</h2>
<ul class="sdb-14__rows">
<li><span class="sdb-14__sha">4c19f2a</span><span>eu-west-1</span><em>Promoted 09:14 · Alex Chen</em></li>
<li><span class="sdb-14__sha">9ab7d10</span><span>eu-west-1</span><em>Building · Priya Sharma</em></li>
<li><span class="sdb-14__sha">17c40be</span><span>us-east-1</span><em>Rolled back · Jordan Lee</em></li>
<li><span class="sdb-14__sha">08e5cc1</span><span>ap-south-1</span><em>Promoted 07:41 · Ada Okafor</em></li>
<li><span class="sdb-14__sha">b7712fd</span><span>eu-west-1</span><em>Superseded · Sam Rivera</em></li>
</ul>
</main>
</div>
</section><section class="sdb-14" aria-label="Meridian admin">
<div class="sdb-14__theme">
<input class="sdb-14__theme-input" type="checkbox" id="sdb-14-theme">
<label class="sdb-14__theme-label" for="sdb-14-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-14__sr">Toggle dark theme</span>
</label>
</div>
<div class="sdb-14__app">
<nav class="sdb-14__rail" aria-label="Meridian admin">
<p class="sdb-14__rail-title">Meridian</p>
<ul class="sdb-14__list">
<li><a class="sdb-14__link" href="#sdb-14-main"><span class="sdb-14__ico" aria-hidden="true">□</span>Overview</a></li>
</ul>
<details class="sdb-14__group" open>
<summary class="sdb-14__summary"><span class="sdb-14__ico" aria-hidden="true">▼</span>Infrastructure</summary>
<ul class="sdb-14__list sdb-14__list--sub">
<li><a class="sdb-14__link sdb-14__link--sub" href="#sdb-14-main">Regions</a></li>
<li><a class="sdb-14__link sdb-14__link--sub" href="#sdb-14-main" aria-current="page">Deployments</a></li>
<li><a class="sdb-14__link sdb-14__link--sub" href="#sdb-14-main">Certificates</a></li>
</ul>
</details>
<details class="sdb-14__group">
<summary class="sdb-14__summary"><span class="sdb-14__ico" aria-hidden="true">▼</span>Access</summary>
<ul class="sdb-14__list sdb-14__list--sub">
<li><a class="sdb-14__link sdb-14__link--sub" href="#sdb-14-main">Members</a></li>
<li><a class="sdb-14__link sdb-14__link--sub" href="#sdb-14-main">API keys</a></li>
<li><a class="sdb-14__link sdb-14__link--sub" href="#sdb-14-main">Audit log</a></li>
</ul>
</details>
<ul class="sdb-14__list">
<li><a class="sdb-14__link" href="#sdb-14-main"><span class="sdb-14__ico" aria-hidden="true">□</span>Billing</a></li>
</ul>
</nav>
<main class="sdb-14__main" id="sdb-14-main">
<p class="sdb-14__crumb">Infrastructure → <span class="sdb-14__crumb-here">Deployments</span></p>
<h2 class="sdb-14__main-h">Deployments</h2>
<ul class="sdb-14__rows">
<li><span class="sdb-14__sha">4c19f2a</span><span>eu-west-1</span><em>Promoted 09:14 · Alex Chen</em></li>
<li><span class="sdb-14__sha">9ab7d10</span><span>eu-west-1</span><em>Building · Priya Sharma</em></li>
<li><span class="sdb-14__sha">17c40be</span><span>us-east-1</span><em>Rolled back · Jordan Lee</em></li>
<li><span class="sdb-14__sha">08e5cc1</span><span>ap-south-1</span><em>Promoted 07:41 · Ada Okafor</em></li>
<li><span class="sdb-14__sha">b7712fd</span><span>eu-west-1</span><em>Superseded · Sam Rivera</em></li>
</ul>
</main>
</div>
</section>.sdb-14 {
--sdb-14-bg: #f2f3f2;
--sdb-14-surf: #fbfbfa;
--sdb-14-surf2: #e7e9e7;
--sdb-14-surf3: #dcdfdc;
--sdb-14-line: #cdd1cd;
--sdb-14-ink: #161a18;
--sdb-14-mut: #5c625f;
--sdb-14-acc: #0d6b62;
--sdb-14-acc: oklch(46% 0.09 190);
--sdb-14-acc-soft: #d5e8e5;
--sdb-14-font-display: "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif;
width: 100%;
min-height: 100vh;
display: block;
position: relative;
box-sizing: border-box;
background: var(--sdb-14-bg);
color: var(--sdb-14-ink);
font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
line-height: 1.55;
}
.sdb-14 *,
.sdb-14 *::before,
.sdb-14 *::after {
box-sizing: border-box;
}
.sdb-14:has(.sdb-14__theme-input:checked) {
--sdb-14-bg: #0c100e;
--sdb-14-surf: #141917;
--sdb-14-surf2: #1b211f;
--sdb-14-surf3: #232b28;
--sdb-14-line: #2b3532;
--sdb-14-ink: #e9efec;
--sdb-14-mut: #94a09c;
--sdb-14-acc: #6fd2c6;
--sdb-14-acc: oklch(82% 0.1 190);
--sdb-14-acc-soft: #14302c;
}
@media (prefers-color-scheme: dark) {
.sdb-14 {
--sdb-14-bg: #0c100e;
--sdb-14-surf: #141917;
--sdb-14-surf2: #1b211f;
--sdb-14-surf3: #232b28;
--sdb-14-line: #2b3532;
--sdb-14-ink: #e9efec;
--sdb-14-mut: #94a09c;
--sdb-14-acc: #6fd2c6;
--sdb-14-acc: oklch(82% 0.1 190);
--sdb-14-acc-soft: #14302c;
}
.sdb-14:has(.sdb-14__theme-input:checked) {
--sdb-14-bg: #f2f3f2;
--sdb-14-surf: #fbfbfa;
--sdb-14-surf2: #e7e9e7;
--sdb-14-surf3: #dcdfdc;
--sdb-14-line: #cdd1cd;
--sdb-14-ink: #161a18;
--sdb-14-mut: #5c625f;
--sdb-14-acc: #0d6b62;
--sdb-14-acc: oklch(46% 0.09 190);
--sdb-14-acc-soft: #d5e8e5;
}
}
@media (prefers-color-scheme: light) {
.sdb-14:has(.sdb-14__theme-input:checked) {
--sdb-14-bg: #0c100e;
--sdb-14-surf: #141917;
--sdb-14-surf2: #1b211f;
--sdb-14-surf3: #232b28;
--sdb-14-line: #2b3532;
--sdb-14-ink: #e9efec;
--sdb-14-mut: #94a09c;
--sdb-14-acc: #6fd2c6;
--sdb-14-acc: oklch(82% 0.1 190);
--sdb-14-acc-soft: #14302c;
}
}
.sdb-14__theme {
position: absolute;
inset-block-start: 0.85rem;
inset-inline-end: 0.85rem;
z-index: 60;
}
.sdb-14__theme-input {
position: absolute;
inset: 0;
inline-size: 100%;
block-size: 100%;
margin: 0;
opacity: 0;
cursor: pointer;
}
.sdb-14__theme-label {
display: grid;
place-items: center;
inline-size: 2.75rem;
block-size: 2.75rem;
border-radius: 0.35rem;
border: 1px solid var(--sdb-14-line);
background: var(--sdb-14-surf);
color: var(--sdb-14-ink);
cursor: pointer;
}
.sdb-14__theme-label svg {
inline-size: 1.15rem;
block-size: 1.15rem;
}
.sdb-14__theme-input:focus-visible + .sdb-14__theme-label {
outline: 2px solid var(--sdb-14-acc);
outline-offset: 2px;
}
.sdb-14__sr {
position: absolute;
inline-size: 1px;
block-size: 1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
}
.sdb-14__app {
display: grid;
grid-template-columns: minmax(0, 1fr);
min-block-size: 100vh;
background: var(--sdb-14-surf);
}
@media (min-width: 46rem) {
.sdb-14__app {
grid-template-columns: minmax(0, 15rem) minmax(0, 1fr);
}
}
.sdb-14__rail {
min-inline-size: 0;
padding: 1rem 0.55rem 1.2rem;
background: var(--sdb-14-surf2);
border-block-end: 1px solid var(--sdb-14-line);
}
@media (min-width: 46rem) {
.sdb-14__rail {
border-block-end: 0;
border-inline-end: 1px solid var(--sdb-14-line);
}
}
.sdb-14__rail-title {
margin: 0 0 0.7rem;
padding-inline: 0.6rem;
font-family: var(--sdb-14-font-display);
font-size: 1.15rem;
}
.sdb-14__list {
list-style: none;
margin: 0;
padding: 0;
}
.sdb-14__link {
position: relative;
display: flex;
align-items: center;
gap: 0.55rem;
min-block-size: 2.75rem;
padding: 0.5rem 0.65rem;
border-radius: 0.3rem;
color: var(--sdb-14-ink);
text-decoration: none;
font-size: 0.91rem;
min-inline-size: 0;
}
.sdb-14__link--sub {
padding-inline-start: 1.9rem;
font-size: 0.88rem;
color: var(--sdb-14-mut);
}
.sdb-14__ico {
flex: none;
font-size: 0.7rem;
color: var(--sdb-14-mut);
}
.sdb-14__link:hover {
background: var(--sdb-14-surf3);
}
.sdb-14__link:focus-visible,
.sdb-14__summary:focus-visible {
outline: 2px solid var(--sdb-14-acc);
outline-offset: -2px;
}
.sdb-14__link[aria-current="page"] {
background: var(--sdb-14-acc-soft);
color: var(--sdb-14-acc);
font-weight: 650;
box-shadow: inset 3px 0 0 var(--sdb-14-acc);
}
.sdb-14__link[aria-current="page"] .sdb-14__ico {
color: var(--sdb-14-acc);
}
.sdb-14__group {
margin-block: 0.15rem;
}
.sdb-14__summary {
display: flex;
align-items: center;
gap: 0.55rem;
min-block-size: 2.75rem;
padding: 0.5rem 0.65rem;
border-radius: 0.3rem;
cursor: pointer;
font-size: 0.91rem;
font-weight: 600;
list-style: none;
}
.sdb-14__summary::-webkit-details-marker {
display: none;
}
.sdb-14__summary .sdb-14__ico {
transition: transform 150ms ease;
}
.sdb-14__group:not([open]) .sdb-14__summary .sdb-14__ico {
transform: rotate(-90deg);
}
.sdb-14__group:has([aria-current="page"]) > .sdb-14__summary::after {
content: "";
margin-inline-start: auto;
inline-size: 0.4rem;
block-size: 0.4rem;
border-radius: 50%;
background: var(--sdb-14-acc);
flex: none;
}
.sdb-14__main {
min-inline-size: 0;
padding: 1.5rem 1.25rem 2.5rem;
align-content: start;
}
@media (min-width: 46rem) {
.sdb-14__main {
padding: 2.4rem 2.25rem 3rem;
}
}
.sdb-14__crumb {
margin: 0 0 0.25rem;
font-size: 0.82rem;
color: var(--sdb-14-mut);
}
.sdb-14__crumb-here {
color: var(--sdb-14-acc);
font-weight: 650;
}
.sdb-14__main-h {
margin: 0 0 1.2rem;
font-family: var(--sdb-14-font-display);
font-size: clamp(1.45rem, 3.6vw, 2.1rem);
line-height: 1.1;
}
.sdb-14__rows {
list-style: none;
margin: 0;
padding: 0;
display: grid;
gap: 1px;
background: var(--sdb-14-line);
border: 1px solid var(--sdb-14-line);
max-inline-size: 48rem;
}
.sdb-14__rows li {
display: flex;
flex-wrap: wrap;
align-items: baseline;
gap: 0.6rem;
padding: 0.7rem 0.85rem;
background: var(--sdb-14-surf);
font-size: 0.88rem;
min-inline-size: 0;
}
.sdb-14__sha {
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 0.82rem;
color: var(--sdb-14-acc);
}
.sdb-14__rows em {
margin-inline-start: auto;
font-style: normal;
font-size: 0.8rem;
color: var(--sdb-14-mut);
}
@media (prefers-reduced-motion: reduce) {
.sdb-14__summary .sdb-14__ico {
transition: none;
}
}.sdb-14 {
--sdb-14-bg: #f2f3f2;
--sdb-14-surf: #fbfbfa;
--sdb-14-surf2: #e7e9e7;
--sdb-14-surf3: #dcdfdc;
--sdb-14-line: #cdd1cd;
--sdb-14-ink: #161a18;
--sdb-14-mut: #5c625f;
--sdb-14-acc: #0d6b62;
--sdb-14-acc: oklch(46% 0.09 190);
--sdb-14-acc-soft: #d5e8e5;
--sdb-14-font-display: "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif;
width: 100%;
min-height: 100vh;
display: block;
position: relative;
box-sizing: border-box;
background: var(--sdb-14-bg);
color: var(--sdb-14-ink);
font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
line-height: 1.55;
}
.sdb-14 *,
.sdb-14 *::before,
.sdb-14 *::after {
box-sizing: border-box;
}
.sdb-14:has(.sdb-14__theme-input:checked) {
--sdb-14-bg: #0c100e;
--sdb-14-surf: #141917;
--sdb-14-surf2: #1b211f;
--sdb-14-surf3: #232b28;
--sdb-14-line: #2b3532;
--sdb-14-ink: #e9efec;
--sdb-14-mut: #94a09c;
--sdb-14-acc: #6fd2c6;
--sdb-14-acc: oklch(82% 0.1 190);
--sdb-14-acc-soft: #14302c;
}
@media (prefers-color-scheme: dark) {
.sdb-14 {
--sdb-14-bg: #0c100e;
--sdb-14-surf: #141917;
--sdb-14-surf2: #1b211f;
--sdb-14-surf3: #232b28;
--sdb-14-line: #2b3532;
--sdb-14-ink: #e9efec;
--sdb-14-mut: #94a09c;
--sdb-14-acc: #6fd2c6;
--sdb-14-acc: oklch(82% 0.1 190);
--sdb-14-acc-soft: #14302c;
}
.sdb-14:has(.sdb-14__theme-input:checked) {
--sdb-14-bg: #f2f3f2;
--sdb-14-surf: #fbfbfa;
--sdb-14-surf2: #e7e9e7;
--sdb-14-surf3: #dcdfdc;
--sdb-14-line: #cdd1cd;
--sdb-14-ink: #161a18;
--sdb-14-mut: #5c625f;
--sdb-14-acc: #0d6b62;
--sdb-14-acc: oklch(46% 0.09 190);
--sdb-14-acc-soft: #d5e8e5;
}
}
@media (prefers-color-scheme: light) {
.sdb-14:has(.sdb-14__theme-input:checked) {
--sdb-14-bg: #0c100e;
--sdb-14-surf: #141917;
--sdb-14-surf2: #1b211f;
--sdb-14-surf3: #232b28;
--sdb-14-line: #2b3532;
--sdb-14-ink: #e9efec;
--sdb-14-mut: #94a09c;
--sdb-14-acc: #6fd2c6;
--sdb-14-acc: oklch(82% 0.1 190);
--sdb-14-acc-soft: #14302c;
}
}
.sdb-14__theme {
position: absolute;
inset-block-start: 0.85rem;
inset-inline-end: 0.85rem;
z-index: 60;
}
.sdb-14__theme-input {
position: absolute;
inset: 0;
inline-size: 100%;
block-size: 100%;
margin: 0;
opacity: 0;
cursor: pointer;
}
.sdb-14__theme-label {
display: grid;
place-items: center;
inline-size: 2.75rem;
block-size: 2.75rem;
border-radius: 0.35rem;
border: 1px solid var(--sdb-14-line);
background: var(--sdb-14-surf);
color: var(--sdb-14-ink);
cursor: pointer;
}
.sdb-14__theme-label svg {
inline-size: 1.15rem;
block-size: 1.15rem;
}
.sdb-14__theme-input:focus-visible + .sdb-14__theme-label {
outline: 2px solid var(--sdb-14-acc);
outline-offset: 2px;
}
.sdb-14__sr {
position: absolute;
inline-size: 1px;
block-size: 1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
}
.sdb-14__app {
display: grid;
grid-template-columns: minmax(0, 1fr);
min-block-size: 100vh;
background: var(--sdb-14-surf);
}
@media (min-width: 46rem) {
.sdb-14__app {
grid-template-columns: minmax(0, 15rem) minmax(0, 1fr);
}
}
.sdb-14__rail {
min-inline-size: 0;
padding: 1rem 0.55rem 1.2rem;
background: var(--sdb-14-surf2);
border-block-end: 1px solid var(--sdb-14-line);
}
@media (min-width: 46rem) {
.sdb-14__rail {
border-block-end: 0;
border-inline-end: 1px solid var(--sdb-14-line);
}
}
.sdb-14__rail-title {
margin: 0 0 0.7rem;
padding-inline: 0.6rem;
font-family: var(--sdb-14-font-display);
font-size: 1.15rem;
}
.sdb-14__list {
list-style: none;
margin: 0;
padding: 0;
}
.sdb-14__link {
position: relative;
display: flex;
align-items: center;
gap: 0.55rem;
min-block-size: 2.75rem;
padding: 0.5rem 0.65rem;
border-radius: 0.3rem;
color: var(--sdb-14-ink);
text-decoration: none;
font-size: 0.91rem;
min-inline-size: 0;
}
.sdb-14__link--sub {
padding-inline-start: 1.9rem;
font-size: 0.88rem;
color: var(--sdb-14-mut);
}
.sdb-14__ico {
flex: none;
font-size: 0.7rem;
color: var(--sdb-14-mut);
}
.sdb-14__link:hover {
background: var(--sdb-14-surf3);
}
.sdb-14__link:focus-visible,
.sdb-14__summary:focus-visible {
outline: 2px solid var(--sdb-14-acc);
outline-offset: -2px;
}
.sdb-14__link[aria-current="page"] {
background: var(--sdb-14-acc-soft);
color: var(--sdb-14-acc);
font-weight: 650;
box-shadow: inset 3px 0 0 var(--sdb-14-acc);
}
.sdb-14__link[aria-current="page"] .sdb-14__ico {
color: var(--sdb-14-acc);
}
.sdb-14__group {
margin-block: 0.15rem;
}
.sdb-14__summary {
display: flex;
align-items: center;
gap: 0.55rem;
min-block-size: 2.75rem;
padding: 0.5rem 0.65rem;
border-radius: 0.3rem;
cursor: pointer;
font-size: 0.91rem;
font-weight: 600;
list-style: none;
}
.sdb-14__summary::-webkit-details-marker {
display: none;
}
.sdb-14__summary .sdb-14__ico {
transition: transform 150ms ease;
}
.sdb-14__group:not([open]) .sdb-14__summary .sdb-14__ico {
transform: rotate(-90deg);
}
.sdb-14__group:has([aria-current="page"]) > .sdb-14__summary::after {
content: "";
margin-inline-start: auto;
inline-size: 0.4rem;
block-size: 0.4rem;
border-radius: 50%;
background: var(--sdb-14-acc);
flex: none;
}
.sdb-14__main {
min-inline-size: 0;
padding: 1.5rem 1.25rem 2.5rem;
align-content: start;
}
@media (min-width: 46rem) {
.sdb-14__main {
padding: 2.4rem 2.25rem 3rem;
}
}
.sdb-14__crumb {
margin: 0 0 0.25rem;
font-size: 0.82rem;
color: var(--sdb-14-mut);
}
.sdb-14__crumb-here {
color: var(--sdb-14-acc);
font-weight: 650;
}
.sdb-14__main-h {
margin: 0 0 1.2rem;
font-family: var(--sdb-14-font-display);
font-size: clamp(1.45rem, 3.6vw, 2.1rem);
line-height: 1.1;
}
.sdb-14__rows {
list-style: none;
margin: 0;
padding: 0;
display: grid;
gap: 1px;
background: var(--sdb-14-line);
border: 1px solid var(--sdb-14-line);
max-inline-size: 48rem;
}
.sdb-14__rows li {
display: flex;
flex-wrap: wrap;
align-items: baseline;
gap: 0.6rem;
padding: 0.7rem 0.85rem;
background: var(--sdb-14-surf);
font-size: 0.88rem;
min-inline-size: 0;
}
.sdb-14__sha {
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 0.82rem;
color: var(--sdb-14-acc);
}
.sdb-14__rows em {
margin-inline-start: auto;
font-style: normal;
font-size: 0.8rem;
color: var(--sdb-14-mut);
}
@media (prefers-reduced-motion: reduce) {
.sdb-14__summary .sdb-14__ico {
transition: none;
}
}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: Dashboard Sidebar with Current Page State
Source: https://codefronts.com/navigation/css-sidebar-navigation/responsive-dashboard-sidebar-navigation-with-submenus/
aria-current="page" as the single source of truth for the active item, with every visual affordance derived from the attribute instead of a parallel class. One state, not two that can disagree: the row fill, the inset marker, the icon colour, the breadcrumb and the group's containment dot all read the same attribute.
## HTML
```html
<section class="sdb-14" aria-label="Meridian admin">
<div class="sdb-14__theme">
<input class="sdb-14__theme-input" type="checkbox" id="sdb-14-theme">
<label class="sdb-14__theme-label" for="sdb-14-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-14__sr">Toggle dark theme</span>
</label>
</div>
<div class="sdb-14__app">
<nav class="sdb-14__rail" aria-label="Meridian admin">
<p class="sdb-14__rail-title">Meridian</p>
<ul class="sdb-14__list">
<li><a class="sdb-14__link" href="#sdb-14-main"><span class="sdb-14__ico" aria-hidden="true">□</span>Overview</a></li>
</ul>
<details class="sdb-14__group" open>
<summary class="sdb-14__summary"><span class="sdb-14__ico" aria-hidden="true">▼</span>Infrastructure</summary>
<ul class="sdb-14__list sdb-14__list--sub">
<li><a class="sdb-14__link sdb-14__link--sub" href="#sdb-14-main">Regions</a></li>
<li><a class="sdb-14__link sdb-14__link--sub" href="#sdb-14-main" aria-current="page">Deployments</a></li>
<li><a class="sdb-14__link sdb-14__link--sub" href="#sdb-14-main">Certificates</a></li>
</ul>
</details>
<details class="sdb-14__group">
<summary class="sdb-14__summary"><span class="sdb-14__ico" aria-hidden="true">▼</span>Access</summary>
<ul class="sdb-14__list sdb-14__list--sub">
<li><a class="sdb-14__link sdb-14__link--sub" href="#sdb-14-main">Members</a></li>
<li><a class="sdb-14__link sdb-14__link--sub" href="#sdb-14-main">API keys</a></li>
<li><a class="sdb-14__link sdb-14__link--sub" href="#sdb-14-main">Audit log</a></li>
</ul>
</details>
<ul class="sdb-14__list">
<li><a class="sdb-14__link" href="#sdb-14-main"><span class="sdb-14__ico" aria-hidden="true">□</span>Billing</a></li>
</ul>
</nav>
<main class="sdb-14__main" id="sdb-14-main">
<p class="sdb-14__crumb">Infrastructure → <span class="sdb-14__crumb-here">Deployments</span></p>
<h2 class="sdb-14__main-h">Deployments</h2>
<ul class="sdb-14__rows">
<li><span class="sdb-14__sha">4c19f2a</span><span>eu-west-1</span><em>Promoted 09:14 · Alex Chen</em></li>
<li><span class="sdb-14__sha">9ab7d10</span><span>eu-west-1</span><em>Building · Priya Sharma</em></li>
<li><span class="sdb-14__sha">17c40be</span><span>us-east-1</span><em>Rolled back · Jordan Lee</em></li>
<li><span class="sdb-14__sha">08e5cc1</span><span>ap-south-1</span><em>Promoted 07:41 · Ada Okafor</em></li>
<li><span class="sdb-14__sha">b7712fd</span><span>eu-west-1</span><em>Superseded · Sam Rivera</em></li>
</ul>
</main>
</div>
</section>
```
## CSS
```css
.sdb-14 {
--sdb-14-bg: #f2f3f2;
--sdb-14-surf: #fbfbfa;
--sdb-14-surf2: #e7e9e7;
--sdb-14-surf3: #dcdfdc;
--sdb-14-line: #cdd1cd;
--sdb-14-ink: #161a18;
--sdb-14-mut: #5c625f;
--sdb-14-acc: #0d6b62;
--sdb-14-acc: oklch(46% 0.09 190);
--sdb-14-acc-soft: #d5e8e5;
--sdb-14-font-display: "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif;
width: 100%;
min-height: 100vh;
display: block;
position: relative;
box-sizing: border-box;
background: var(--sdb-14-bg);
color: var(--sdb-14-ink);
font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
line-height: 1.55;
}
.sdb-14 *,
.sdb-14 *::before,
.sdb-14 *::after {
box-sizing: border-box;
}
.sdb-14:has(.sdb-14__theme-input:checked) {
--sdb-14-bg: #0c100e;
--sdb-14-surf: #141917;
--sdb-14-surf2: #1b211f;
--sdb-14-surf3: #232b28;
--sdb-14-line: #2b3532;
--sdb-14-ink: #e9efec;
--sdb-14-mut: #94a09c;
--sdb-14-acc: #6fd2c6;
--sdb-14-acc: oklch(82% 0.1 190);
--sdb-14-acc-soft: #14302c;
}
@media (prefers-color-scheme: dark) {
.sdb-14 {
--sdb-14-bg: #0c100e;
--sdb-14-surf: #141917;
--sdb-14-surf2: #1b211f;
--sdb-14-surf3: #232b28;
--sdb-14-line: #2b3532;
--sdb-14-ink: #e9efec;
--sdb-14-mut: #94a09c;
--sdb-14-acc: #6fd2c6;
--sdb-14-acc: oklch(82% 0.1 190);
--sdb-14-acc-soft: #14302c;
}
.sdb-14:has(.sdb-14__theme-input:checked) {
--sdb-14-bg: #f2f3f2;
--sdb-14-surf: #fbfbfa;
--sdb-14-surf2: #e7e9e7;
--sdb-14-surf3: #dcdfdc;
--sdb-14-line: #cdd1cd;
--sdb-14-ink: #161a18;
--sdb-14-mut: #5c625f;
--sdb-14-acc: #0d6b62;
--sdb-14-acc: oklch(46% 0.09 190);
--sdb-14-acc-soft: #d5e8e5;
}
}
@media (prefers-color-scheme: light) {
.sdb-14:has(.sdb-14__theme-input:checked) {
--sdb-14-bg: #0c100e;
--sdb-14-surf: #141917;
--sdb-14-surf2: #1b211f;
--sdb-14-surf3: #232b28;
--sdb-14-line: #2b3532;
--sdb-14-ink: #e9efec;
--sdb-14-mut: #94a09c;
--sdb-14-acc: #6fd2c6;
--sdb-14-acc: oklch(82% 0.1 190);
--sdb-14-acc-soft: #14302c;
}
}
.sdb-14__theme {
position: absolute;
inset-block-start: 0.85rem;
inset-inline-end: 0.85rem;
z-index: 60;
}
.sdb-14__theme-input {
position: absolute;
inset: 0;
inline-size: 100%;
block-size: 100%;
margin: 0;
opacity: 0;
cursor: pointer;
}
.sdb-14__theme-label {
display: grid;
place-items: center;
inline-size: 2.75rem;
block-size: 2.75rem;
border-radius: 0.35rem;
border: 1px solid var(--sdb-14-line);
background: var(--sdb-14-surf);
color: var(--sdb-14-ink);
cursor: pointer;
}
.sdb-14__theme-label svg {
inline-size: 1.15rem;
block-size: 1.15rem;
}
.sdb-14__theme-input:focus-visible + .sdb-14__theme-label {
outline: 2px solid var(--sdb-14-acc);
outline-offset: 2px;
}
.sdb-14__sr {
position: absolute;
inline-size: 1px;
block-size: 1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
}
.sdb-14__app {
display: grid;
grid-template-columns: minmax(0, 1fr);
min-block-size: 100vh;
background: var(--sdb-14-surf);
}
@media (min-width: 46rem) {
.sdb-14__app {
grid-template-columns: minmax(0, 15rem) minmax(0, 1fr);
}
}
.sdb-14__rail {
min-inline-size: 0;
padding: 1rem 0.55rem 1.2rem;
background: var(--sdb-14-surf2);
border-block-end: 1px solid var(--sdb-14-line);
}
@media (min-width: 46rem) {
.sdb-14__rail {
border-block-end: 0;
border-inline-end: 1px solid var(--sdb-14-line);
}
}
.sdb-14__rail-title {
margin: 0 0 0.7rem;
padding-inline: 0.6rem;
font-family: var(--sdb-14-font-display);
font-size: 1.15rem;
}
.sdb-14__list {
list-style: none;
margin: 0;
padding: 0;
}
.sdb-14__link {
position: relative;
display: flex;
align-items: center;
gap: 0.55rem;
min-block-size: 2.75rem;
padding: 0.5rem 0.65rem;
border-radius: 0.3rem;
color: var(--sdb-14-ink);
text-decoration: none;
font-size: 0.91rem;
min-inline-size: 0;
}
.sdb-14__link--sub {
padding-inline-start: 1.9rem;
font-size: 0.88rem;
color: var(--sdb-14-mut);
}
.sdb-14__ico {
flex: none;
font-size: 0.7rem;
color: var(--sdb-14-mut);
}
.sdb-14__link:hover {
background: var(--sdb-14-surf3);
}
.sdb-14__link:focus-visible,
.sdb-14__summary:focus-visible {
outline: 2px solid var(--sdb-14-acc);
outline-offset: -2px;
}
.sdb-14__link[aria-current="page"] {
background: var(--sdb-14-acc-soft);
color: var(--sdb-14-acc);
font-weight: 650;
box-shadow: inset 3px 0 0 var(--sdb-14-acc);
}
.sdb-14__link[aria-current="page"] .sdb-14__ico {
color: var(--sdb-14-acc);
}
.sdb-14__group {
margin-block: 0.15rem;
}
.sdb-14__summary {
display: flex;
align-items: center;
gap: 0.55rem;
min-block-size: 2.75rem;
padding: 0.5rem 0.65rem;
border-radius: 0.3rem;
cursor: pointer;
font-size: 0.91rem;
font-weight: 600;
list-style: none;
}
.sdb-14__summary::-webkit-details-marker {
display: none;
}
.sdb-14__summary .sdb-14__ico {
transition: transform 150ms ease;
}
.sdb-14__group:not([open]) .sdb-14__summary .sdb-14__ico {
transform: rotate(-90deg);
}
.sdb-14__group:has([aria-current="page"]) > .sdb-14__summary::after {
content: "";
margin-inline-start: auto;
inline-size: 0.4rem;
block-size: 0.4rem;
border-radius: 50%;
background: var(--sdb-14-acc);
flex: none;
}
.sdb-14__main {
min-inline-size: 0;
padding: 1.5rem 1.25rem 2.5rem;
align-content: start;
}
@media (min-width: 46rem) {
.sdb-14__main {
padding: 2.4rem 2.25rem 3rem;
}
}
.sdb-14__crumb {
margin: 0 0 0.25rem;
font-size: 0.82rem;
color: var(--sdb-14-mut);
}
.sdb-14__crumb-here {
color: var(--sdb-14-acc);
font-weight: 650;
}
.sdb-14__main-h {
margin: 0 0 1.2rem;
font-family: var(--sdb-14-font-display);
font-size: clamp(1.45rem, 3.6vw, 2.1rem);
line-height: 1.1;
}
.sdb-14__rows {
list-style: none;
margin: 0;
padding: 0;
display: grid;
gap: 1px;
background: var(--sdb-14-line);
border: 1px solid var(--sdb-14-line);
max-inline-size: 48rem;
}
.sdb-14__rows li {
display: flex;
flex-wrap: wrap;
align-items: baseline;
gap: 0.6rem;
padding: 0.7rem 0.85rem;
background: var(--sdb-14-surf);
font-size: 0.88rem;
min-inline-size: 0;
}
.sdb-14__sha {
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 0.82rem;
color: var(--sdb-14-acc);
}
.sdb-14__rows em {
margin-inline-start: auto;
font-style: normal;
font-size: 0.8rem;
color: var(--sdb-14-mut);
}
@media (prefers-reduced-motion: reduce) {
.sdb-14__summary .sdb-14__ico {
transition: none;
}
}
```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: Dashboard Sidebar with Current Page State
Source: https://codefronts.com/navigation/css-sidebar-navigation/responsive-dashboard-sidebar-navigation-with-submenus/
aria-current="page" as the single source of truth for the active item, with every visual affordance derived from the attribute instead of a parallel class. One state, not two that can disagree: the row fill, the inset marker, the icon colour, the breadcrumb and the group's containment dot all read the same attribute.
## HTML
```html
<section class="sdb-14" aria-label="Meridian admin">
<div class="sdb-14__theme">
<input class="sdb-14__theme-input" type="checkbox" id="sdb-14-theme">
<label class="sdb-14__theme-label" for="sdb-14-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-14__sr">Toggle dark theme</span>
</label>
</div>
<div class="sdb-14__app">
<nav class="sdb-14__rail" aria-label="Meridian admin">
<p class="sdb-14__rail-title">Meridian</p>
<ul class="sdb-14__list">
<li><a class="sdb-14__link" href="#sdb-14-main"><span class="sdb-14__ico" aria-hidden="true">□</span>Overview</a></li>
</ul>
<details class="sdb-14__group" open>
<summary class="sdb-14__summary"><span class="sdb-14__ico" aria-hidden="true">▼</span>Infrastructure</summary>
<ul class="sdb-14__list sdb-14__list--sub">
<li><a class="sdb-14__link sdb-14__link--sub" href="#sdb-14-main">Regions</a></li>
<li><a class="sdb-14__link sdb-14__link--sub" href="#sdb-14-main" aria-current="page">Deployments</a></li>
<li><a class="sdb-14__link sdb-14__link--sub" href="#sdb-14-main">Certificates</a></li>
</ul>
</details>
<details class="sdb-14__group">
<summary class="sdb-14__summary"><span class="sdb-14__ico" aria-hidden="true">▼</span>Access</summary>
<ul class="sdb-14__list sdb-14__list--sub">
<li><a class="sdb-14__link sdb-14__link--sub" href="#sdb-14-main">Members</a></li>
<li><a class="sdb-14__link sdb-14__link--sub" href="#sdb-14-main">API keys</a></li>
<li><a class="sdb-14__link sdb-14__link--sub" href="#sdb-14-main">Audit log</a></li>
</ul>
</details>
<ul class="sdb-14__list">
<li><a class="sdb-14__link" href="#sdb-14-main"><span class="sdb-14__ico" aria-hidden="true">□</span>Billing</a></li>
</ul>
</nav>
<main class="sdb-14__main" id="sdb-14-main">
<p class="sdb-14__crumb">Infrastructure → <span class="sdb-14__crumb-here">Deployments</span></p>
<h2 class="sdb-14__main-h">Deployments</h2>
<ul class="sdb-14__rows">
<li><span class="sdb-14__sha">4c19f2a</span><span>eu-west-1</span><em>Promoted 09:14 · Alex Chen</em></li>
<li><span class="sdb-14__sha">9ab7d10</span><span>eu-west-1</span><em>Building · Priya Sharma</em></li>
<li><span class="sdb-14__sha">17c40be</span><span>us-east-1</span><em>Rolled back · Jordan Lee</em></li>
<li><span class="sdb-14__sha">08e5cc1</span><span>ap-south-1</span><em>Promoted 07:41 · Ada Okafor</em></li>
<li><span class="sdb-14__sha">b7712fd</span><span>eu-west-1</span><em>Superseded · Sam Rivera</em></li>
</ul>
</main>
</div>
</section>
```
## CSS
```css
.sdb-14 {
--sdb-14-bg: #f2f3f2;
--sdb-14-surf: #fbfbfa;
--sdb-14-surf2: #e7e9e7;
--sdb-14-surf3: #dcdfdc;
--sdb-14-line: #cdd1cd;
--sdb-14-ink: #161a18;
--sdb-14-mut: #5c625f;
--sdb-14-acc: #0d6b62;
--sdb-14-acc: oklch(46% 0.09 190);
--sdb-14-acc-soft: #d5e8e5;
--sdb-14-font-display: "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif;
width: 100%;
min-height: 100vh;
display: block;
position: relative;
box-sizing: border-box;
background: var(--sdb-14-bg);
color: var(--sdb-14-ink);
font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
line-height: 1.55;
}
.sdb-14 *,
.sdb-14 *::before,
.sdb-14 *::after {
box-sizing: border-box;
}
.sdb-14:has(.sdb-14__theme-input:checked) {
--sdb-14-bg: #0c100e;
--sdb-14-surf: #141917;
--sdb-14-surf2: #1b211f;
--sdb-14-surf3: #232b28;
--sdb-14-line: #2b3532;
--sdb-14-ink: #e9efec;
--sdb-14-mut: #94a09c;
--sdb-14-acc: #6fd2c6;
--sdb-14-acc: oklch(82% 0.1 190);
--sdb-14-acc-soft: #14302c;
}
@media (prefers-color-scheme: dark) {
.sdb-14 {
--sdb-14-bg: #0c100e;
--sdb-14-surf: #141917;
--sdb-14-surf2: #1b211f;
--sdb-14-surf3: #232b28;
--sdb-14-line: #2b3532;
--sdb-14-ink: #e9efec;
--sdb-14-mut: #94a09c;
--sdb-14-acc: #6fd2c6;
--sdb-14-acc: oklch(82% 0.1 190);
--sdb-14-acc-soft: #14302c;
}
.sdb-14:has(.sdb-14__theme-input:checked) {
--sdb-14-bg: #f2f3f2;
--sdb-14-surf: #fbfbfa;
--sdb-14-surf2: #e7e9e7;
--sdb-14-surf3: #dcdfdc;
--sdb-14-line: #cdd1cd;
--sdb-14-ink: #161a18;
--sdb-14-mut: #5c625f;
--sdb-14-acc: #0d6b62;
--sdb-14-acc: oklch(46% 0.09 190);
--sdb-14-acc-soft: #d5e8e5;
}
}
@media (prefers-color-scheme: light) {
.sdb-14:has(.sdb-14__theme-input:checked) {
--sdb-14-bg: #0c100e;
--sdb-14-surf: #141917;
--sdb-14-surf2: #1b211f;
--sdb-14-surf3: #232b28;
--sdb-14-line: #2b3532;
--sdb-14-ink: #e9efec;
--sdb-14-mut: #94a09c;
--sdb-14-acc: #6fd2c6;
--sdb-14-acc: oklch(82% 0.1 190);
--sdb-14-acc-soft: #14302c;
}
}
.sdb-14__theme {
position: absolute;
inset-block-start: 0.85rem;
inset-inline-end: 0.85rem;
z-index: 60;
}
.sdb-14__theme-input {
position: absolute;
inset: 0;
inline-size: 100%;
block-size: 100%;
margin: 0;
opacity: 0;
cursor: pointer;
}
.sdb-14__theme-label {
display: grid;
place-items: center;
inline-size: 2.75rem;
block-size: 2.75rem;
border-radius: 0.35rem;
border: 1px solid var(--sdb-14-line);
background: var(--sdb-14-surf);
color: var(--sdb-14-ink);
cursor: pointer;
}
.sdb-14__theme-label svg {
inline-size: 1.15rem;
block-size: 1.15rem;
}
.sdb-14__theme-input:focus-visible + .sdb-14__theme-label {
outline: 2px solid var(--sdb-14-acc);
outline-offset: 2px;
}
.sdb-14__sr {
position: absolute;
inline-size: 1px;
block-size: 1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
}
.sdb-14__app {
display: grid;
grid-template-columns: minmax(0, 1fr);
min-block-size: 100vh;
background: var(--sdb-14-surf);
}
@media (min-width: 46rem) {
.sdb-14__app {
grid-template-columns: minmax(0, 15rem) minmax(0, 1fr);
}
}
.sdb-14__rail {
min-inline-size: 0;
padding: 1rem 0.55rem 1.2rem;
background: var(--sdb-14-surf2);
border-block-end: 1px solid var(--sdb-14-line);
}
@media (min-width: 46rem) {
.sdb-14__rail {
border-block-end: 0;
border-inline-end: 1px solid var(--sdb-14-line);
}
}
.sdb-14__rail-title {
margin: 0 0 0.7rem;
padding-inline: 0.6rem;
font-family: var(--sdb-14-font-display);
font-size: 1.15rem;
}
.sdb-14__list {
list-style: none;
margin: 0;
padding: 0;
}
.sdb-14__link {
position: relative;
display: flex;
align-items: center;
gap: 0.55rem;
min-block-size: 2.75rem;
padding: 0.5rem 0.65rem;
border-radius: 0.3rem;
color: var(--sdb-14-ink);
text-decoration: none;
font-size: 0.91rem;
min-inline-size: 0;
}
.sdb-14__link--sub {
padding-inline-start: 1.9rem;
font-size: 0.88rem;
color: var(--sdb-14-mut);
}
.sdb-14__ico {
flex: none;
font-size: 0.7rem;
color: var(--sdb-14-mut);
}
.sdb-14__link:hover {
background: var(--sdb-14-surf3);
}
.sdb-14__link:focus-visible,
.sdb-14__summary:focus-visible {
outline: 2px solid var(--sdb-14-acc);
outline-offset: -2px;
}
.sdb-14__link[aria-current="page"] {
background: var(--sdb-14-acc-soft);
color: var(--sdb-14-acc);
font-weight: 650;
box-shadow: inset 3px 0 0 var(--sdb-14-acc);
}
.sdb-14__link[aria-current="page"] .sdb-14__ico {
color: var(--sdb-14-acc);
}
.sdb-14__group {
margin-block: 0.15rem;
}
.sdb-14__summary {
display: flex;
align-items: center;
gap: 0.55rem;
min-block-size: 2.75rem;
padding: 0.5rem 0.65rem;
border-radius: 0.3rem;
cursor: pointer;
font-size: 0.91rem;
font-weight: 600;
list-style: none;
}
.sdb-14__summary::-webkit-details-marker {
display: none;
}
.sdb-14__summary .sdb-14__ico {
transition: transform 150ms ease;
}
.sdb-14__group:not([open]) .sdb-14__summary .sdb-14__ico {
transform: rotate(-90deg);
}
.sdb-14__group:has([aria-current="page"]) > .sdb-14__summary::after {
content: "";
margin-inline-start: auto;
inline-size: 0.4rem;
block-size: 0.4rem;
border-radius: 50%;
background: var(--sdb-14-acc);
flex: none;
}
.sdb-14__main {
min-inline-size: 0;
padding: 1.5rem 1.25rem 2.5rem;
align-content: start;
}
@media (min-width: 46rem) {
.sdb-14__main {
padding: 2.4rem 2.25rem 3rem;
}
}
.sdb-14__crumb {
margin: 0 0 0.25rem;
font-size: 0.82rem;
color: var(--sdb-14-mut);
}
.sdb-14__crumb-here {
color: var(--sdb-14-acc);
font-weight: 650;
}
.sdb-14__main-h {
margin: 0 0 1.2rem;
font-family: var(--sdb-14-font-display);
font-size: clamp(1.45rem, 3.6vw, 2.1rem);
line-height: 1.1;
}
.sdb-14__rows {
list-style: none;
margin: 0;
padding: 0;
display: grid;
gap: 1px;
background: var(--sdb-14-line);
border: 1px solid var(--sdb-14-line);
max-inline-size: 48rem;
}
.sdb-14__rows li {
display: flex;
flex-wrap: wrap;
align-items: baseline;
gap: 0.6rem;
padding: 0.7rem 0.85rem;
background: var(--sdb-14-surf);
font-size: 0.88rem;
min-inline-size: 0;
}
.sdb-14__sha {
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 0.82rem;
color: var(--sdb-14-acc);
}
.sdb-14__rows em {
margin-inline-start: auto;
font-style: normal;
font-size: 0.8rem;
color: var(--sdb-14-mut);
}
@media (prefers-reduced-motion: reduce) {
.sdb-14__summary .sdb-14__ico {
transition: none;
}
}
```How this works
Two sources of truth for one state is the bug, and it is extremely common. The usual shape is a router that adds class="active" and, later, an accessibility pass that adds aria-current="page". Now two things must be updated together forever. The one that gets forgotten is almost always the attribute, because nothing on screen changes when it is wrong — so the sidebar looks right and announces nothing.
Deriving the visual from the attribute removes the failure mode entirely. Every rule in this demo selects [aria-current="page"]: the row fill, the inline marker, the icon tint. If the attribute is missing, the item does not look active either — the bug becomes visible instantly, in the place a developer is already looking. Attribute selectors have the same specificity as a class, so there is no cost to switching.
The same trick handles the ancestor. :has([aria-current="page"]) on a group derives "contains the current page" from the leaf's attribute rather than from a second flag. Nothing to set, nothing to unset, and a collapsed group still shows a containment dot. That pairs with demo 09, which covers what the ancestor should announce as well as show.
The trap: the wrong value, or too many of them. aria-current is not a boolean — page for the current page, step in a flow, true as a generic fallback. And exactly one element per navigation should carry it; marking both a parent and its child announces two current pages. Also do not put it on a non-link: a <div> with aria-current is not navigable and the attribute has nothing to attach to.
Make it yours
- Restyle the active row by editing the single
[aria-current="page"]block; every affordance is grouped there deliberately. - Change the marker from an inline bar to a leading dot or a full-bleed fill without touching any markup.
- Swap
pageforstepif the rail is a wizard rather than navigation, and update the selector to match. - Move the containment dot to a count badge if the number of items in the group matters more than its state.
- Add a
:has()rule to tint the whole group header when it contains the current page, which pairs with demo 09. - Open the containing group by default when rendering server-side so the current item is visible without interaction.
Gotchas — read before shipping
- Keeping both a class and
aria-currentguarantees they drift. The visual one is the one you notice; the announced one is the one that matters. aria-current="true"on a page link is a weaker announcement than"page". Use the specific value where one exists.- Marking both a submenu item and its parent announces two current pages, which is contradictory rather than helpfully redundant.
aria-currenton a<div>or<span>does nothing useful — there is no link for the user to act on and often no way to reach it at all.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 15.4+ | 121+ | 111+ |
the :has() group derivation sets the floor with oklch() (Chrome 105 / Safari 15.4 / Firefox 121 for :has()). Attribute selectors themselves are universally supported, so engines without :has() keep the whole active-item treatment and lose only the containment dot on collapsed groups.