04 / 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
Sidebar Navigation Is Not a Tab List
Applying role="tablist" to site navigation because it looks like a vertical strip is a frequent and serious mistake. Tabs promise that activating an item swaps a panel in place; navigation promises a page change. Get it wrong and a screen-reader user is told the wrong thing about what will happen, and inherits arrow-key conventions navigation should not have. The rail here is the navigation build: a named landmark, a list of links, and aria-current="page" on the active one.
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-04" aria-label="Quill learning centre">
<div class="sdb-04__theme">
<input class="sdb-04__theme-input" type="checkbox" id="sdb-04-theme">
<label class="sdb-04__theme-label" for="sdb-04-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-04__sr">Toggle dark theme</span>
</label>
</div>
<div class="sdb-04__app">
<nav class="sdb-04__rail" aria-label="Quill learning centre">
<p class="sdb-04__brand">Quill</p>
<ul class="sdb-04__list">
<li><a class="sdb-04__link" href="#sdb-04-main" aria-current="page">Getting started</a></li>
<li><a class="sdb-04__link" href="#sdb-04-main">Deployments</a></li>
<li><a class="sdb-04__link" href="#sdb-04-main">Billing</a></li>
<li><a class="sdb-04__link" href="#sdb-04-main">Audit log</a></li>
<li><a class="sdb-04__link" href="#sdb-04-main">Integrations</a></li>
<li><a class="sdb-04__link" href="#sdb-04-main">Certification</a></li>
</ul>
</nav>
<main class="sdb-04__main" id="sdb-04-main">
<p class="sdb-04__eyebrow">Course · 6 lessons</p>
<h2 class="sdb-04__title">Getting started</h2>
<div class="sdb-04__lessons">
<article class="sdb-04__lesson">
<p class="sdb-04__lesson-n">01</p>
<h3 class="sdb-04__lesson-h">Create your first workspace</h3>
<p class="sdb-04__lesson-m">8 min · video</p>
</article>
<article class="sdb-04__lesson">
<p class="sdb-04__lesson-n">02</p>
<h3 class="sdb-04__lesson-h">Invite a teammate and set roles</h3>
<p class="sdb-04__lesson-m">5 min · reading</p>
</article>
<article class="sdb-04__lesson">
<p class="sdb-04__lesson-n">03</p>
<h3 class="sdb-04__lesson-h">Connect a repository</h3>
<p class="sdb-04__lesson-m">11 min · walkthrough</p>
</article>
<article class="sdb-04__lesson">
<p class="sdb-04__lesson-n">04</p>
<h3 class="sdb-04__lesson-h">Ship a preview environment</h3>
<p class="sdb-04__lesson-m">14 min · walkthrough</p>
</article>
</div>
</main>
</div>
</section><section class="sdb-04" aria-label="Quill learning centre">
<div class="sdb-04__theme">
<input class="sdb-04__theme-input" type="checkbox" id="sdb-04-theme">
<label class="sdb-04__theme-label" for="sdb-04-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-04__sr">Toggle dark theme</span>
</label>
</div>
<div class="sdb-04__app">
<nav class="sdb-04__rail" aria-label="Quill learning centre">
<p class="sdb-04__brand">Quill</p>
<ul class="sdb-04__list">
<li><a class="sdb-04__link" href="#sdb-04-main" aria-current="page">Getting started</a></li>
<li><a class="sdb-04__link" href="#sdb-04-main">Deployments</a></li>
<li><a class="sdb-04__link" href="#sdb-04-main">Billing</a></li>
<li><a class="sdb-04__link" href="#sdb-04-main">Audit log</a></li>
<li><a class="sdb-04__link" href="#sdb-04-main">Integrations</a></li>
<li><a class="sdb-04__link" href="#sdb-04-main">Certification</a></li>
</ul>
</nav>
<main class="sdb-04__main" id="sdb-04-main">
<p class="sdb-04__eyebrow">Course · 6 lessons</p>
<h2 class="sdb-04__title">Getting started</h2>
<div class="sdb-04__lessons">
<article class="sdb-04__lesson">
<p class="sdb-04__lesson-n">01</p>
<h3 class="sdb-04__lesson-h">Create your first workspace</h3>
<p class="sdb-04__lesson-m">8 min · video</p>
</article>
<article class="sdb-04__lesson">
<p class="sdb-04__lesson-n">02</p>
<h3 class="sdb-04__lesson-h">Invite a teammate and set roles</h3>
<p class="sdb-04__lesson-m">5 min · reading</p>
</article>
<article class="sdb-04__lesson">
<p class="sdb-04__lesson-n">03</p>
<h3 class="sdb-04__lesson-h">Connect a repository</h3>
<p class="sdb-04__lesson-m">11 min · walkthrough</p>
</article>
<article class="sdb-04__lesson">
<p class="sdb-04__lesson-n">04</p>
<h3 class="sdb-04__lesson-h">Ship a preview environment</h3>
<p class="sdb-04__lesson-m">14 min · walkthrough</p>
</article>
</div>
</main>
</div>
</section>.sdb-04 {
--sdb-04-bg: #f4f4f5;
--sdb-04-surf: #ffffff;
--sdb-04-surf2: #ebebed;
--sdb-04-line: #d4d4d8;
--sdb-04-ink: #18181b;
--sdb-04-mut: #5f5f68;
--sdb-04-acc: #6d3ce0;
--sdb-04-acc: oklch(52% 0.22 291);
--sdb-04-acc-soft: #ece4fd;
--sdb-04-font-display: "Archivo Narrow", "Roboto Condensed", "Helvetica Neue Condensed", ui-sans-serif, system-ui, sans-serif;
width: 100%;
min-height: 100vh;
display: block;
position: relative;
box-sizing: border-box;
background: var(--sdb-04-bg);
color: var(--sdb-04-ink);
font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
line-height: 1.55;
}
.sdb-04 *,
.sdb-04 *::before,
.sdb-04 *::after {
box-sizing: border-box;
}
.sdb-04:has(.sdb-04__theme-input:checked) {
--sdb-04-bg: #0e0e11;
--sdb-04-surf: #17171b;
--sdb-04-surf2: #1f1f25;
--sdb-04-line: #2c2c33;
--sdb-04-ink: #f2f2f5;
--sdb-04-mut: #9d9da8;
--sdb-04-acc: #b79bff;
--sdb-04-acc: oklch(80% 0.13 292);
--sdb-04-acc-soft: #241a3d;
}
@media (prefers-color-scheme: dark) {
.sdb-04 {
--sdb-04-bg: #0e0e11;
--sdb-04-surf: #17171b;
--sdb-04-surf2: #1f1f25;
--sdb-04-line: #2c2c33;
--sdb-04-ink: #f2f2f5;
--sdb-04-mut: #9d9da8;
--sdb-04-acc: #b79bff;
--sdb-04-acc: oklch(80% 0.13 292);
--sdb-04-acc-soft: #241a3d;
}
.sdb-04:has(.sdb-04__theme-input:checked) {
--sdb-04-bg: #f4f4f5;
--sdb-04-surf: #ffffff;
--sdb-04-surf2: #ebebed;
--sdb-04-line: #d4d4d8;
--sdb-04-ink: #18181b;
--sdb-04-mut: #5f5f68;
--sdb-04-acc: #6d3ce0;
--sdb-04-acc: oklch(52% 0.22 291);
--sdb-04-acc-soft: #ece4fd;
}
}
@media (prefers-color-scheme: light) {
.sdb-04:has(.sdb-04__theme-input:checked) {
--sdb-04-bg: #0e0e11;
--sdb-04-surf: #17171b;
--sdb-04-surf2: #1f1f25;
--sdb-04-line: #2c2c33;
--sdb-04-ink: #f2f2f5;
--sdb-04-mut: #9d9da8;
--sdb-04-acc: #b79bff;
--sdb-04-acc: oklch(80% 0.13 292);
--sdb-04-acc-soft: #241a3d;
}
}
.sdb-04__theme {
position: absolute;
inset-block-start: 0.85rem;
inset-inline-end: 0.85rem;
z-index: 60;
}
.sdb-04__theme-input {
position: absolute;
inset: 0;
inline-size: 100%;
block-size: 100%;
margin: 0;
opacity: 0;
cursor: pointer;
}
.sdb-04__theme-label {
display: grid;
place-items: center;
inline-size: 2.75rem;
block-size: 2.75rem;
border: 0;
background: var(--sdb-04-surf2);
color: var(--sdb-04-ink);
cursor: pointer;
}
.sdb-04__theme-label svg {
inline-size: 1.15rem;
block-size: 1.15rem;
}
.sdb-04__theme-input:focus-visible + .sdb-04__theme-label {
outline: 2px solid var(--sdb-04-acc);
outline-offset: 2px;
}
.sdb-04__sr {
position: absolute;
inline-size: 1px;
block-size: 1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
}
.sdb-04__app {
display: grid;
grid-template-columns: minmax(0, 1fr);
min-block-size: 100vh;
}
@media (min-width: 46rem) {
.sdb-04__app {
grid-template-columns: 15rem minmax(0, 1fr);
}
}
.sdb-04__rail {
min-inline-size: 0;
padding: 1rem 0.5rem;
background: var(--sdb-04-surf);
border-block-end: 1px solid var(--sdb-04-line);
}
@media (min-width: 46rem) {
.sdb-04__rail {
border-block-end: 0;
border-inline-end: 1px solid var(--sdb-04-line);
}
}
.sdb-04__brand {
margin: 0 0 0.9rem;
padding-inline: 0.8rem;
font-family: var(--sdb-04-font-display);
font-size: 1.05rem;
font-weight: 700;
letter-spacing: 0.18em;
text-transform: uppercase;
}
.sdb-04__list {
list-style: none;
margin: 0;
padding: 0;
display: grid;
gap: 2px;
}
.sdb-04__link {
display: flex;
align-items: center;
min-block-size: 2.75rem;
padding: 0.5rem 0.8rem;
background: var(--sdb-04-surf);
color: var(--sdb-04-ink);
text-decoration: none;
font-size: 0.92rem;
min-inline-size: 0;
}
.sdb-04__link:hover {
background: var(--sdb-04-acc-soft);
}
.sdb-04__link:focus-visible {
outline: 2px solid var(--sdb-04-acc);
outline-offset: -2px;
}
.sdb-04__link[aria-current="page"] {
background: var(--sdb-04-acc-soft);
color: var(--sdb-04-acc);
font-weight: 650;
box-shadow: inset 3px 0 0 var(--sdb-04-acc);
}
.sdb-04__main {
min-inline-size: 0;
padding: 1.5rem 1.25rem 2.5rem;
align-content: start;
}
@media (min-width: 46rem) {
.sdb-04__main {
padding: 2.5rem 2.25rem 3rem;
}
}
.sdb-04__eyebrow {
margin: 0 0 0.35rem;
font-family: var(--sdb-04-font-display);
font-size: 0.8rem;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--sdb-04-mut);
}
.sdb-04__title {
margin: 0 0 1.4rem;
font-family: var(--sdb-04-font-display);
font-size: clamp(1.6rem, 4.4vw, 2.4rem);
line-height: 1.05;
text-transform: uppercase;
}
.sdb-04__lessons {
display: grid;
grid-template-columns: minmax(0, 1fr);
gap: 2px;
background: var(--sdb-04-line);
border: 1px solid var(--sdb-04-line);
}
@media (min-width: 52rem) {
.sdb-04__lessons {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
.sdb-04__lesson {
min-inline-size: 0;
padding: 1rem;
background: var(--sdb-04-surf);
}
.sdb-04__lesson-n {
margin: 0 0 0.4rem;
font-family: var(--sdb-04-font-display);
font-size: 0.8rem;
letter-spacing: 0.12em;
color: var(--sdb-04-acc);
}
.sdb-04__lesson-h {
margin: 0 0 0.3rem;
font-size: 1rem;
line-height: 1.25;
}
.sdb-04__lesson-m {
margin: 0;
font-size: 0.83rem;
color: var(--sdb-04-mut);
}.sdb-04 {
--sdb-04-bg: #f4f4f5;
--sdb-04-surf: #ffffff;
--sdb-04-surf2: #ebebed;
--sdb-04-line: #d4d4d8;
--sdb-04-ink: #18181b;
--sdb-04-mut: #5f5f68;
--sdb-04-acc: #6d3ce0;
--sdb-04-acc: oklch(52% 0.22 291);
--sdb-04-acc-soft: #ece4fd;
--sdb-04-font-display: "Archivo Narrow", "Roboto Condensed", "Helvetica Neue Condensed", ui-sans-serif, system-ui, sans-serif;
width: 100%;
min-height: 100vh;
display: block;
position: relative;
box-sizing: border-box;
background: var(--sdb-04-bg);
color: var(--sdb-04-ink);
font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
line-height: 1.55;
}
.sdb-04 *,
.sdb-04 *::before,
.sdb-04 *::after {
box-sizing: border-box;
}
.sdb-04:has(.sdb-04__theme-input:checked) {
--sdb-04-bg: #0e0e11;
--sdb-04-surf: #17171b;
--sdb-04-surf2: #1f1f25;
--sdb-04-line: #2c2c33;
--sdb-04-ink: #f2f2f5;
--sdb-04-mut: #9d9da8;
--sdb-04-acc: #b79bff;
--sdb-04-acc: oklch(80% 0.13 292);
--sdb-04-acc-soft: #241a3d;
}
@media (prefers-color-scheme: dark) {
.sdb-04 {
--sdb-04-bg: #0e0e11;
--sdb-04-surf: #17171b;
--sdb-04-surf2: #1f1f25;
--sdb-04-line: #2c2c33;
--sdb-04-ink: #f2f2f5;
--sdb-04-mut: #9d9da8;
--sdb-04-acc: #b79bff;
--sdb-04-acc: oklch(80% 0.13 292);
--sdb-04-acc-soft: #241a3d;
}
.sdb-04:has(.sdb-04__theme-input:checked) {
--sdb-04-bg: #f4f4f5;
--sdb-04-surf: #ffffff;
--sdb-04-surf2: #ebebed;
--sdb-04-line: #d4d4d8;
--sdb-04-ink: #18181b;
--sdb-04-mut: #5f5f68;
--sdb-04-acc: #6d3ce0;
--sdb-04-acc: oklch(52% 0.22 291);
--sdb-04-acc-soft: #ece4fd;
}
}
@media (prefers-color-scheme: light) {
.sdb-04:has(.sdb-04__theme-input:checked) {
--sdb-04-bg: #0e0e11;
--sdb-04-surf: #17171b;
--sdb-04-surf2: #1f1f25;
--sdb-04-line: #2c2c33;
--sdb-04-ink: #f2f2f5;
--sdb-04-mut: #9d9da8;
--sdb-04-acc: #b79bff;
--sdb-04-acc: oklch(80% 0.13 292);
--sdb-04-acc-soft: #241a3d;
}
}
.sdb-04__theme {
position: absolute;
inset-block-start: 0.85rem;
inset-inline-end: 0.85rem;
z-index: 60;
}
.sdb-04__theme-input {
position: absolute;
inset: 0;
inline-size: 100%;
block-size: 100%;
margin: 0;
opacity: 0;
cursor: pointer;
}
.sdb-04__theme-label {
display: grid;
place-items: center;
inline-size: 2.75rem;
block-size: 2.75rem;
border: 0;
background: var(--sdb-04-surf2);
color: var(--sdb-04-ink);
cursor: pointer;
}
.sdb-04__theme-label svg {
inline-size: 1.15rem;
block-size: 1.15rem;
}
.sdb-04__theme-input:focus-visible + .sdb-04__theme-label {
outline: 2px solid var(--sdb-04-acc);
outline-offset: 2px;
}
.sdb-04__sr {
position: absolute;
inline-size: 1px;
block-size: 1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
}
.sdb-04__app {
display: grid;
grid-template-columns: minmax(0, 1fr);
min-block-size: 100vh;
}
@media (min-width: 46rem) {
.sdb-04__app {
grid-template-columns: 15rem minmax(0, 1fr);
}
}
.sdb-04__rail {
min-inline-size: 0;
padding: 1rem 0.5rem;
background: var(--sdb-04-surf);
border-block-end: 1px solid var(--sdb-04-line);
}
@media (min-width: 46rem) {
.sdb-04__rail {
border-block-end: 0;
border-inline-end: 1px solid var(--sdb-04-line);
}
}
.sdb-04__brand {
margin: 0 0 0.9rem;
padding-inline: 0.8rem;
font-family: var(--sdb-04-font-display);
font-size: 1.05rem;
font-weight: 700;
letter-spacing: 0.18em;
text-transform: uppercase;
}
.sdb-04__list {
list-style: none;
margin: 0;
padding: 0;
display: grid;
gap: 2px;
}
.sdb-04__link {
display: flex;
align-items: center;
min-block-size: 2.75rem;
padding: 0.5rem 0.8rem;
background: var(--sdb-04-surf);
color: var(--sdb-04-ink);
text-decoration: none;
font-size: 0.92rem;
min-inline-size: 0;
}
.sdb-04__link:hover {
background: var(--sdb-04-acc-soft);
}
.sdb-04__link:focus-visible {
outline: 2px solid var(--sdb-04-acc);
outline-offset: -2px;
}
.sdb-04__link[aria-current="page"] {
background: var(--sdb-04-acc-soft);
color: var(--sdb-04-acc);
font-weight: 650;
box-shadow: inset 3px 0 0 var(--sdb-04-acc);
}
.sdb-04__main {
min-inline-size: 0;
padding: 1.5rem 1.25rem 2.5rem;
align-content: start;
}
@media (min-width: 46rem) {
.sdb-04__main {
padding: 2.5rem 2.25rem 3rem;
}
}
.sdb-04__eyebrow {
margin: 0 0 0.35rem;
font-family: var(--sdb-04-font-display);
font-size: 0.8rem;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--sdb-04-mut);
}
.sdb-04__title {
margin: 0 0 1.4rem;
font-family: var(--sdb-04-font-display);
font-size: clamp(1.6rem, 4.4vw, 2.4rem);
line-height: 1.05;
text-transform: uppercase;
}
.sdb-04__lessons {
display: grid;
grid-template-columns: minmax(0, 1fr);
gap: 2px;
background: var(--sdb-04-line);
border: 1px solid var(--sdb-04-line);
}
@media (min-width: 52rem) {
.sdb-04__lessons {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
.sdb-04__lesson {
min-inline-size: 0;
padding: 1rem;
background: var(--sdb-04-surf);
}
.sdb-04__lesson-n {
margin: 0 0 0.4rem;
font-family: var(--sdb-04-font-display);
font-size: 0.8rem;
letter-spacing: 0.12em;
color: var(--sdb-04-acc);
}
.sdb-04__lesson-h {
margin: 0 0 0.3rem;
font-size: 1rem;
line-height: 1.25;
}
.sdb-04__lesson-m {
margin: 0;
font-size: 0.83rem;
color: var(--sdb-04-mut);
}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: Sidebar Navigation Is Not a Tab List
Source: https://codefronts.com/navigation/css-sidebar-navigation/sidebar-navigation-is-not-a-tab-list/
Applying role="tablist" to site navigation because it looks like a vertical strip is a frequent and serious mistake. Tabs promise that activating an item swaps a panel in place; navigation promises a page change. Get it wrong and a screen-reader user is told the wrong thing about what will happen, and inherits arrow-key conventions navigation should not have. The rail here is the navigation build: a named landmark, a list of links, and aria-current="page" on the active one.
## HTML
```html
<section class="sdb-04" aria-label="Quill learning centre">
<div class="sdb-04__theme">
<input class="sdb-04__theme-input" type="checkbox" id="sdb-04-theme">
<label class="sdb-04__theme-label" for="sdb-04-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-04__sr">Toggle dark theme</span>
</label>
</div>
<div class="sdb-04__app">
<nav class="sdb-04__rail" aria-label="Quill learning centre">
<p class="sdb-04__brand">Quill</p>
<ul class="sdb-04__list">
<li><a class="sdb-04__link" href="#sdb-04-main" aria-current="page">Getting started</a></li>
<li><a class="sdb-04__link" href="#sdb-04-main">Deployments</a></li>
<li><a class="sdb-04__link" href="#sdb-04-main">Billing</a></li>
<li><a class="sdb-04__link" href="#sdb-04-main">Audit log</a></li>
<li><a class="sdb-04__link" href="#sdb-04-main">Integrations</a></li>
<li><a class="sdb-04__link" href="#sdb-04-main">Certification</a></li>
</ul>
</nav>
<main class="sdb-04__main" id="sdb-04-main">
<p class="sdb-04__eyebrow">Course · 6 lessons</p>
<h2 class="sdb-04__title">Getting started</h2>
<div class="sdb-04__lessons">
<article class="sdb-04__lesson">
<p class="sdb-04__lesson-n">01</p>
<h3 class="sdb-04__lesson-h">Create your first workspace</h3>
<p class="sdb-04__lesson-m">8 min · video</p>
</article>
<article class="sdb-04__lesson">
<p class="sdb-04__lesson-n">02</p>
<h3 class="sdb-04__lesson-h">Invite a teammate and set roles</h3>
<p class="sdb-04__lesson-m">5 min · reading</p>
</article>
<article class="sdb-04__lesson">
<p class="sdb-04__lesson-n">03</p>
<h3 class="sdb-04__lesson-h">Connect a repository</h3>
<p class="sdb-04__lesson-m">11 min · walkthrough</p>
</article>
<article class="sdb-04__lesson">
<p class="sdb-04__lesson-n">04</p>
<h3 class="sdb-04__lesson-h">Ship a preview environment</h3>
<p class="sdb-04__lesson-m">14 min · walkthrough</p>
</article>
</div>
</main>
</div>
</section>
```
## CSS
```css
.sdb-04 {
--sdb-04-bg: #f4f4f5;
--sdb-04-surf: #ffffff;
--sdb-04-surf2: #ebebed;
--sdb-04-line: #d4d4d8;
--sdb-04-ink: #18181b;
--sdb-04-mut: #5f5f68;
--sdb-04-acc: #6d3ce0;
--sdb-04-acc: oklch(52% 0.22 291);
--sdb-04-acc-soft: #ece4fd;
--sdb-04-font-display: "Archivo Narrow", "Roboto Condensed", "Helvetica Neue Condensed", ui-sans-serif, system-ui, sans-serif;
width: 100%;
min-height: 100vh;
display: block;
position: relative;
box-sizing: border-box;
background: var(--sdb-04-bg);
color: var(--sdb-04-ink);
font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
line-height: 1.55;
}
.sdb-04 *,
.sdb-04 *::before,
.sdb-04 *::after {
box-sizing: border-box;
}
.sdb-04:has(.sdb-04__theme-input:checked) {
--sdb-04-bg: #0e0e11;
--sdb-04-surf: #17171b;
--sdb-04-surf2: #1f1f25;
--sdb-04-line: #2c2c33;
--sdb-04-ink: #f2f2f5;
--sdb-04-mut: #9d9da8;
--sdb-04-acc: #b79bff;
--sdb-04-acc: oklch(80% 0.13 292);
--sdb-04-acc-soft: #241a3d;
}
@media (prefers-color-scheme: dark) {
.sdb-04 {
--sdb-04-bg: #0e0e11;
--sdb-04-surf: #17171b;
--sdb-04-surf2: #1f1f25;
--sdb-04-line: #2c2c33;
--sdb-04-ink: #f2f2f5;
--sdb-04-mut: #9d9da8;
--sdb-04-acc: #b79bff;
--sdb-04-acc: oklch(80% 0.13 292);
--sdb-04-acc-soft: #241a3d;
}
.sdb-04:has(.sdb-04__theme-input:checked) {
--sdb-04-bg: #f4f4f5;
--sdb-04-surf: #ffffff;
--sdb-04-surf2: #ebebed;
--sdb-04-line: #d4d4d8;
--sdb-04-ink: #18181b;
--sdb-04-mut: #5f5f68;
--sdb-04-acc: #6d3ce0;
--sdb-04-acc: oklch(52% 0.22 291);
--sdb-04-acc-soft: #ece4fd;
}
}
@media (prefers-color-scheme: light) {
.sdb-04:has(.sdb-04__theme-input:checked) {
--sdb-04-bg: #0e0e11;
--sdb-04-surf: #17171b;
--sdb-04-surf2: #1f1f25;
--sdb-04-line: #2c2c33;
--sdb-04-ink: #f2f2f5;
--sdb-04-mut: #9d9da8;
--sdb-04-acc: #b79bff;
--sdb-04-acc: oklch(80% 0.13 292);
--sdb-04-acc-soft: #241a3d;
}
}
.sdb-04__theme {
position: absolute;
inset-block-start: 0.85rem;
inset-inline-end: 0.85rem;
z-index: 60;
}
.sdb-04__theme-input {
position: absolute;
inset: 0;
inline-size: 100%;
block-size: 100%;
margin: 0;
opacity: 0;
cursor: pointer;
}
.sdb-04__theme-label {
display: grid;
place-items: center;
inline-size: 2.75rem;
block-size: 2.75rem;
border: 0;
background: var(--sdb-04-surf2);
color: var(--sdb-04-ink);
cursor: pointer;
}
.sdb-04__theme-label svg {
inline-size: 1.15rem;
block-size: 1.15rem;
}
.sdb-04__theme-input:focus-visible + .sdb-04__theme-label {
outline: 2px solid var(--sdb-04-acc);
outline-offset: 2px;
}
.sdb-04__sr {
position: absolute;
inline-size: 1px;
block-size: 1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
}
.sdb-04__app {
display: grid;
grid-template-columns: minmax(0, 1fr);
min-block-size: 100vh;
}
@media (min-width: 46rem) {
.sdb-04__app {
grid-template-columns: 15rem minmax(0, 1fr);
}
}
.sdb-04__rail {
min-inline-size: 0;
padding: 1rem 0.5rem;
background: var(--sdb-04-surf);
border-block-end: 1px solid var(--sdb-04-line);
}
@media (min-width: 46rem) {
.sdb-04__rail {
border-block-end: 0;
border-inline-end: 1px solid var(--sdb-04-line);
}
}
.sdb-04__brand {
margin: 0 0 0.9rem;
padding-inline: 0.8rem;
font-family: var(--sdb-04-font-display);
font-size: 1.05rem;
font-weight: 700;
letter-spacing: 0.18em;
text-transform: uppercase;
}
.sdb-04__list {
list-style: none;
margin: 0;
padding: 0;
display: grid;
gap: 2px;
}
.sdb-04__link {
display: flex;
align-items: center;
min-block-size: 2.75rem;
padding: 0.5rem 0.8rem;
background: var(--sdb-04-surf);
color: var(--sdb-04-ink);
text-decoration: none;
font-size: 0.92rem;
min-inline-size: 0;
}
.sdb-04__link:hover {
background: var(--sdb-04-acc-soft);
}
.sdb-04__link:focus-visible {
outline: 2px solid var(--sdb-04-acc);
outline-offset: -2px;
}
.sdb-04__link[aria-current="page"] {
background: var(--sdb-04-acc-soft);
color: var(--sdb-04-acc);
font-weight: 650;
box-shadow: inset 3px 0 0 var(--sdb-04-acc);
}
.sdb-04__main {
min-inline-size: 0;
padding: 1.5rem 1.25rem 2.5rem;
align-content: start;
}
@media (min-width: 46rem) {
.sdb-04__main {
padding: 2.5rem 2.25rem 3rem;
}
}
.sdb-04__eyebrow {
margin: 0 0 0.35rem;
font-family: var(--sdb-04-font-display);
font-size: 0.8rem;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--sdb-04-mut);
}
.sdb-04__title {
margin: 0 0 1.4rem;
font-family: var(--sdb-04-font-display);
font-size: clamp(1.6rem, 4.4vw, 2.4rem);
line-height: 1.05;
text-transform: uppercase;
}
.sdb-04__lessons {
display: grid;
grid-template-columns: minmax(0, 1fr);
gap: 2px;
background: var(--sdb-04-line);
border: 1px solid var(--sdb-04-line);
}
@media (min-width: 52rem) {
.sdb-04__lessons {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
.sdb-04__lesson {
min-inline-size: 0;
padding: 1rem;
background: var(--sdb-04-surf);
}
.sdb-04__lesson-n {
margin: 0 0 0.4rem;
font-family: var(--sdb-04-font-display);
font-size: 0.8rem;
letter-spacing: 0.12em;
color: var(--sdb-04-acc);
}
.sdb-04__lesson-h {
margin: 0 0 0.3rem;
font-size: 1rem;
line-height: 1.25;
}
.sdb-04__lesson-m {
margin: 0;
font-size: 0.83rem;
color: var(--sdb-04-mut);
}
```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: Sidebar Navigation Is Not a Tab List
Source: https://codefronts.com/navigation/css-sidebar-navigation/sidebar-navigation-is-not-a-tab-list/
Applying role="tablist" to site navigation because it looks like a vertical strip is a frequent and serious mistake. Tabs promise that activating an item swaps a panel in place; navigation promises a page change. Get it wrong and a screen-reader user is told the wrong thing about what will happen, and inherits arrow-key conventions navigation should not have. The rail here is the navigation build: a named landmark, a list of links, and aria-current="page" on the active one.
## HTML
```html
<section class="sdb-04" aria-label="Quill learning centre">
<div class="sdb-04__theme">
<input class="sdb-04__theme-input" type="checkbox" id="sdb-04-theme">
<label class="sdb-04__theme-label" for="sdb-04-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-04__sr">Toggle dark theme</span>
</label>
</div>
<div class="sdb-04__app">
<nav class="sdb-04__rail" aria-label="Quill learning centre">
<p class="sdb-04__brand">Quill</p>
<ul class="sdb-04__list">
<li><a class="sdb-04__link" href="#sdb-04-main" aria-current="page">Getting started</a></li>
<li><a class="sdb-04__link" href="#sdb-04-main">Deployments</a></li>
<li><a class="sdb-04__link" href="#sdb-04-main">Billing</a></li>
<li><a class="sdb-04__link" href="#sdb-04-main">Audit log</a></li>
<li><a class="sdb-04__link" href="#sdb-04-main">Integrations</a></li>
<li><a class="sdb-04__link" href="#sdb-04-main">Certification</a></li>
</ul>
</nav>
<main class="sdb-04__main" id="sdb-04-main">
<p class="sdb-04__eyebrow">Course · 6 lessons</p>
<h2 class="sdb-04__title">Getting started</h2>
<div class="sdb-04__lessons">
<article class="sdb-04__lesson">
<p class="sdb-04__lesson-n">01</p>
<h3 class="sdb-04__lesson-h">Create your first workspace</h3>
<p class="sdb-04__lesson-m">8 min · video</p>
</article>
<article class="sdb-04__lesson">
<p class="sdb-04__lesson-n">02</p>
<h3 class="sdb-04__lesson-h">Invite a teammate and set roles</h3>
<p class="sdb-04__lesson-m">5 min · reading</p>
</article>
<article class="sdb-04__lesson">
<p class="sdb-04__lesson-n">03</p>
<h3 class="sdb-04__lesson-h">Connect a repository</h3>
<p class="sdb-04__lesson-m">11 min · walkthrough</p>
</article>
<article class="sdb-04__lesson">
<p class="sdb-04__lesson-n">04</p>
<h3 class="sdb-04__lesson-h">Ship a preview environment</h3>
<p class="sdb-04__lesson-m">14 min · walkthrough</p>
</article>
</div>
</main>
</div>
</section>
```
## CSS
```css
.sdb-04 {
--sdb-04-bg: #f4f4f5;
--sdb-04-surf: #ffffff;
--sdb-04-surf2: #ebebed;
--sdb-04-line: #d4d4d8;
--sdb-04-ink: #18181b;
--sdb-04-mut: #5f5f68;
--sdb-04-acc: #6d3ce0;
--sdb-04-acc: oklch(52% 0.22 291);
--sdb-04-acc-soft: #ece4fd;
--sdb-04-font-display: "Archivo Narrow", "Roboto Condensed", "Helvetica Neue Condensed", ui-sans-serif, system-ui, sans-serif;
width: 100%;
min-height: 100vh;
display: block;
position: relative;
box-sizing: border-box;
background: var(--sdb-04-bg);
color: var(--sdb-04-ink);
font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
line-height: 1.55;
}
.sdb-04 *,
.sdb-04 *::before,
.sdb-04 *::after {
box-sizing: border-box;
}
.sdb-04:has(.sdb-04__theme-input:checked) {
--sdb-04-bg: #0e0e11;
--sdb-04-surf: #17171b;
--sdb-04-surf2: #1f1f25;
--sdb-04-line: #2c2c33;
--sdb-04-ink: #f2f2f5;
--sdb-04-mut: #9d9da8;
--sdb-04-acc: #b79bff;
--sdb-04-acc: oklch(80% 0.13 292);
--sdb-04-acc-soft: #241a3d;
}
@media (prefers-color-scheme: dark) {
.sdb-04 {
--sdb-04-bg: #0e0e11;
--sdb-04-surf: #17171b;
--sdb-04-surf2: #1f1f25;
--sdb-04-line: #2c2c33;
--sdb-04-ink: #f2f2f5;
--sdb-04-mut: #9d9da8;
--sdb-04-acc: #b79bff;
--sdb-04-acc: oklch(80% 0.13 292);
--sdb-04-acc-soft: #241a3d;
}
.sdb-04:has(.sdb-04__theme-input:checked) {
--sdb-04-bg: #f4f4f5;
--sdb-04-surf: #ffffff;
--sdb-04-surf2: #ebebed;
--sdb-04-line: #d4d4d8;
--sdb-04-ink: #18181b;
--sdb-04-mut: #5f5f68;
--sdb-04-acc: #6d3ce0;
--sdb-04-acc: oklch(52% 0.22 291);
--sdb-04-acc-soft: #ece4fd;
}
}
@media (prefers-color-scheme: light) {
.sdb-04:has(.sdb-04__theme-input:checked) {
--sdb-04-bg: #0e0e11;
--sdb-04-surf: #17171b;
--sdb-04-surf2: #1f1f25;
--sdb-04-line: #2c2c33;
--sdb-04-ink: #f2f2f5;
--sdb-04-mut: #9d9da8;
--sdb-04-acc: #b79bff;
--sdb-04-acc: oklch(80% 0.13 292);
--sdb-04-acc-soft: #241a3d;
}
}
.sdb-04__theme {
position: absolute;
inset-block-start: 0.85rem;
inset-inline-end: 0.85rem;
z-index: 60;
}
.sdb-04__theme-input {
position: absolute;
inset: 0;
inline-size: 100%;
block-size: 100%;
margin: 0;
opacity: 0;
cursor: pointer;
}
.sdb-04__theme-label {
display: grid;
place-items: center;
inline-size: 2.75rem;
block-size: 2.75rem;
border: 0;
background: var(--sdb-04-surf2);
color: var(--sdb-04-ink);
cursor: pointer;
}
.sdb-04__theme-label svg {
inline-size: 1.15rem;
block-size: 1.15rem;
}
.sdb-04__theme-input:focus-visible + .sdb-04__theme-label {
outline: 2px solid var(--sdb-04-acc);
outline-offset: 2px;
}
.sdb-04__sr {
position: absolute;
inline-size: 1px;
block-size: 1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
}
.sdb-04__app {
display: grid;
grid-template-columns: minmax(0, 1fr);
min-block-size: 100vh;
}
@media (min-width: 46rem) {
.sdb-04__app {
grid-template-columns: 15rem minmax(0, 1fr);
}
}
.sdb-04__rail {
min-inline-size: 0;
padding: 1rem 0.5rem;
background: var(--sdb-04-surf);
border-block-end: 1px solid var(--sdb-04-line);
}
@media (min-width: 46rem) {
.sdb-04__rail {
border-block-end: 0;
border-inline-end: 1px solid var(--sdb-04-line);
}
}
.sdb-04__brand {
margin: 0 0 0.9rem;
padding-inline: 0.8rem;
font-family: var(--sdb-04-font-display);
font-size: 1.05rem;
font-weight: 700;
letter-spacing: 0.18em;
text-transform: uppercase;
}
.sdb-04__list {
list-style: none;
margin: 0;
padding: 0;
display: grid;
gap: 2px;
}
.sdb-04__link {
display: flex;
align-items: center;
min-block-size: 2.75rem;
padding: 0.5rem 0.8rem;
background: var(--sdb-04-surf);
color: var(--sdb-04-ink);
text-decoration: none;
font-size: 0.92rem;
min-inline-size: 0;
}
.sdb-04__link:hover {
background: var(--sdb-04-acc-soft);
}
.sdb-04__link:focus-visible {
outline: 2px solid var(--sdb-04-acc);
outline-offset: -2px;
}
.sdb-04__link[aria-current="page"] {
background: var(--sdb-04-acc-soft);
color: var(--sdb-04-acc);
font-weight: 650;
box-shadow: inset 3px 0 0 var(--sdb-04-acc);
}
.sdb-04__main {
min-inline-size: 0;
padding: 1.5rem 1.25rem 2.5rem;
align-content: start;
}
@media (min-width: 46rem) {
.sdb-04__main {
padding: 2.5rem 2.25rem 3rem;
}
}
.sdb-04__eyebrow {
margin: 0 0 0.35rem;
font-family: var(--sdb-04-font-display);
font-size: 0.8rem;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--sdb-04-mut);
}
.sdb-04__title {
margin: 0 0 1.4rem;
font-family: var(--sdb-04-font-display);
font-size: clamp(1.6rem, 4.4vw, 2.4rem);
line-height: 1.05;
text-transform: uppercase;
}
.sdb-04__lessons {
display: grid;
grid-template-columns: minmax(0, 1fr);
gap: 2px;
background: var(--sdb-04-line);
border: 1px solid var(--sdb-04-line);
}
@media (min-width: 52rem) {
.sdb-04__lessons {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
.sdb-04__lesson {
min-inline-size: 0;
padding: 1rem;
background: var(--sdb-04-surf);
}
.sdb-04__lesson-n {
margin: 0 0 0.4rem;
font-family: var(--sdb-04-font-display);
font-size: 0.8rem;
letter-spacing: 0.12em;
color: var(--sdb-04-acc);
}
.sdb-04__lesson-h {
margin: 0 0 0.3rem;
font-size: 1rem;
line-height: 1.25;
}
.sdb-04__lesson-m {
margin: 0;
font-size: 0.83rem;
color: var(--sdb-04-mut);
}
```How this works
The two roles make different promises and users act on them. role="tab" tells a screen-reader user "tab 2 of 5, selected" — that this control belongs to a set, that one member is active, and crucially that activating another will replace a panel without leaving the page. A link inside <nav> announces "link, current page", which promises a navigation. When the markup and the outcome disagree, the user's mental model breaks in a way they cannot see or diagnose.
The wrong role also drags in the wrong keyboard contract. The tabs pattern expects arrow keys to move between tabs, usually activating as they go, with the whole tablist as a single tab stop. Navigation expects each link to be its own tab stop and arrow keys to do nothing special. Ship role="tablist" on a sidebar and either you implement arrow-key activation — so arrowing past an item navigates away, which is hostile — or you do not, and you have a tablist that fails its own pattern.
The decision rule is one question about what happens on activation. If content swaps in place with no URL change and no page load, it is tabs, and css-vertical-tabs covers those rails including the icon-only mini sidebar. If the URL changes, or a route loads, or the item is a link a user could open in a new window, it is navigation — <nav>, <ul>, <a>, aria-current="page".
The trap is that the visual is identical, so nothing catches it. Both rails in this demo are styled the same way; a screenshot review, a design QA pass and a visual regression suite all pass. The only thing that surfaces the difference is reading the accessibility tree or listening to the announcement, which is why this belongs in a code review checklist rather than a design one. A cheap check: if the sidebar item cannot sensibly be middle-clicked into a new tab, ask why it is in a sidebar at all.
Make it yours
- Swap the sample labels for your own product areas; the rail is a plain list of links.
- Delete the lesson grid and keep the rail alone if navigation is all you need.
- Add
aria-current="true"instead of"page"if the rail marks a step in a flow rather than the current page. - Restyle the active item by editing the
[aria-current="page"]rule; there is no parallel active class to keep in sync. - Give the nav rail a group heading with
aria-labelledbyinstead ofaria-labelwhen the heading is already visible on screen. - Turn the rail into a collapsible group with
<details>, as in demo 01, without changing the role decision.
Gotchas — read before shipping
role="tablist"on navigation announces "tab 2 of 5" and promises a panel swap. The user then finds the page has changed, which is exactly the kind of mismatch that makes people stop trusting the announcements.aria-selectedis meaningless outside a composite widget like tabs, a listbox or a grid. On a link it is either ignored or reported as a state the user cannot act on.- A tablist implies arrow-key movement and a single tab stop. Half-implementing it — the role without the keyboard behaviour — is worse than not using it, because assistive technology now advertises interactions that do not work.
- Both markups look identical on screen, so visual review, screenshot diffs and design QA all miss this entirely. It only shows up in the accessibility tree or in a screen reader.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 15.4+ | 121+ | 111+ |
oklch() and the :has() theme toggle set the floor; the roles and attributes discussed have no version floor at all and the rail is flexbox and grid, Baseline since 2017. Engines below the oklch() floor take the sRGB hex declared first on every token and keep the operating-system colour scheme.