13 / 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
Icon Rail Sidebar with Accessible Labels
An icon-only rail where every item keeps an accessible name, and the tooltip is deliberately not that name. The two jobs are separate: the name is permanent clipped text that assistive technology always reads; the tooltip is a visual affordance for anyone who does not recognise the glyph. title is wrong for either — announced inconsistently, absent on touch, and its tooltip cannot be hovered or dismissed.
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-13" aria-label="Aperture media library">
<div class="sdb-13__theme">
<input class="sdb-13__theme-input" type="checkbox" id="sdb-13-theme">
<label class="sdb-13__theme-label" for="sdb-13-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-13__sr">Toggle dark theme</span>
</label>
</div>
<div class="sdb-13__app">
<nav class="sdb-13__rail" aria-label="Aperture library">
<ul class="sdb-13__list">
<li><a class="sdb-13__link" href="#sdb-13-main" aria-current="page">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><rect x="4" y="5" width="16" height="14" rx="2" fill="none" stroke="currentColor" stroke-width="1.7"/><path d="M4 15l4-4 4 4 3-3 5 5" fill="none" stroke="currentColor" stroke-width="1.7"/></svg>
<span class="sdb-13__sr">All media</span><span class="sdb-13__tip" aria-hidden="true">All media</span></a></li>
<li><a class="sdb-13__link" href="#sdb-13-main">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M4 7h6l2 2h8v9H4Z" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"/></svg>
<span class="sdb-13__sr">Collections</span><span class="sdb-13__tip" aria-hidden="true">Collections</span></a></li>
<li><a class="sdb-13__link" href="#sdb-13-main">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="11" cy="11" r="6" fill="none" stroke="currentColor" stroke-width="1.7"/><path d="M15.5 15.5L20 20" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/></svg>
<span class="sdb-13__sr">Saved searches</span><span class="sdb-13__tip" aria-hidden="true">Saved searches</span></a></li>
<li><a class="sdb-13__link" href="#sdb-13-main">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M12 4l7 4v8l-7 4-7-4V8Z" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"/></svg>
<span class="sdb-13__sr">Renditions</span><span class="sdb-13__tip" aria-hidden="true">Renditions</span></a></li>
<li><a class="sdb-13__link" href="#sdb-13-main">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M5 19V5h9l5 5v9Z" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"/><path d="M9 13h6" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/></svg>
<span class="sdb-13__sr">Licences</span><span class="sdb-13__tip" aria-hidden="true">Licences</span></a></li>
<li><a class="sdb-13__link" href="#sdb-13-main">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M6 4h12v16l-6-4-6 4Z" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"/></svg>
<span class="sdb-13__sr">Archive</span><span class="sdb-13__tip" aria-hidden="true">Archive</span></a></li>
</ul>
</nav>
<main class="sdb-13__main" id="sdb-13-main">
<div class="sdb-13__main-head">
<h2 class="sdb-13__main-h">All media</h2>
<p class="sdb-13__main-m">1,842 assets · 41 GB · last ingest 14:02</p>
</div>
<div class="sdb-13__grid" aria-hidden="true">
<span></span><span></span><span></span><span></span><span></span><span></span>
<span></span><span></span><span></span><span></span><span></span><span></span>
<span></span><span></span><span></span><span></span><span></span><span></span>
</div>
</main>
</div>
</section><section class="sdb-13" aria-label="Aperture media library">
<div class="sdb-13__theme">
<input class="sdb-13__theme-input" type="checkbox" id="sdb-13-theme">
<label class="sdb-13__theme-label" for="sdb-13-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-13__sr">Toggle dark theme</span>
</label>
</div>
<div class="sdb-13__app">
<nav class="sdb-13__rail" aria-label="Aperture library">
<ul class="sdb-13__list">
<li><a class="sdb-13__link" href="#sdb-13-main" aria-current="page">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><rect x="4" y="5" width="16" height="14" rx="2" fill="none" stroke="currentColor" stroke-width="1.7"/><path d="M4 15l4-4 4 4 3-3 5 5" fill="none" stroke="currentColor" stroke-width="1.7"/></svg>
<span class="sdb-13__sr">All media</span><span class="sdb-13__tip" aria-hidden="true">All media</span></a></li>
<li><a class="sdb-13__link" href="#sdb-13-main">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M4 7h6l2 2h8v9H4Z" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"/></svg>
<span class="sdb-13__sr">Collections</span><span class="sdb-13__tip" aria-hidden="true">Collections</span></a></li>
<li><a class="sdb-13__link" href="#sdb-13-main">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="11" cy="11" r="6" fill="none" stroke="currentColor" stroke-width="1.7"/><path d="M15.5 15.5L20 20" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/></svg>
<span class="sdb-13__sr">Saved searches</span><span class="sdb-13__tip" aria-hidden="true">Saved searches</span></a></li>
<li><a class="sdb-13__link" href="#sdb-13-main">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M12 4l7 4v8l-7 4-7-4V8Z" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"/></svg>
<span class="sdb-13__sr">Renditions</span><span class="sdb-13__tip" aria-hidden="true">Renditions</span></a></li>
<li><a class="sdb-13__link" href="#sdb-13-main">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M5 19V5h9l5 5v9Z" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"/><path d="M9 13h6" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/></svg>
<span class="sdb-13__sr">Licences</span><span class="sdb-13__tip" aria-hidden="true">Licences</span></a></li>
<li><a class="sdb-13__link" href="#sdb-13-main">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M6 4h12v16l-6-4-6 4Z" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"/></svg>
<span class="sdb-13__sr">Archive</span><span class="sdb-13__tip" aria-hidden="true">Archive</span></a></li>
</ul>
</nav>
<main class="sdb-13__main" id="sdb-13-main">
<div class="sdb-13__main-head">
<h2 class="sdb-13__main-h">All media</h2>
<p class="sdb-13__main-m">1,842 assets · 41 GB · last ingest 14:02</p>
</div>
<div class="sdb-13__grid" aria-hidden="true">
<span></span><span></span><span></span><span></span><span></span><span></span>
<span></span><span></span><span></span><span></span><span></span><span></span>
<span></span><span></span><span></span><span></span><span></span><span></span>
</div>
</main>
</div>
</section>.sdb-13 {
--sdb-13-bg: #f4f4f5;
--sdb-13-surf: #ffffff;
--sdb-13-surf2: #e9e9ec;
--sdb-13-line: #d3d3d8;
--sdb-13-ink: #131316;
--sdb-13-mut: #5c5c66;
--sdb-13-acc: #0f5c8c;
--sdb-13-acc: oklch(45% 0.1 232);
--sdb-13-acc-soft: #dceaf4;
--sdb-13-w: 4rem;
--sdb-13-font-display: "Roboto Condensed", "Archivo Narrow", "Helvetica Neue", ui-sans-serif, system-ui, sans-serif;
width: 100%;
min-height: 100vh;
display: block;
position: relative;
box-sizing: border-box;
background: var(--sdb-13-bg);
color: var(--sdb-13-ink);
font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
line-height: 1.55;
}
.sdb-13 *,
.sdb-13 *::before,
.sdb-13 *::after {
box-sizing: border-box;
}
.sdb-13:has(.sdb-13__theme-input:checked) {
--sdb-13-bg: #0d0d0f;
--sdb-13-surf: #15151a;
--sdb-13-surf2: #1d1d23;
--sdb-13-line: #2a2a31;
--sdb-13-ink: #eeeef1;
--sdb-13-mut: #9a9aa4;
--sdb-13-acc: #6fbde8;
--sdb-13-acc: oklch(78% 0.1 232);
--sdb-13-acc-soft: #16283a;
}
@media (prefers-color-scheme: dark) {
.sdb-13 {
--sdb-13-bg: #0d0d0f;
--sdb-13-surf: #15151a;
--sdb-13-surf2: #1d1d23;
--sdb-13-line: #2a2a31;
--sdb-13-ink: #eeeef1;
--sdb-13-mut: #9a9aa4;
--sdb-13-acc: #6fbde8;
--sdb-13-acc: oklch(78% 0.1 232);
--sdb-13-acc-soft: #16283a;
}
.sdb-13:has(.sdb-13__theme-input:checked) {
--sdb-13-bg: #f4f4f5;
--sdb-13-surf: #ffffff;
--sdb-13-surf2: #e9e9ec;
--sdb-13-line: #d3d3d8;
--sdb-13-ink: #131316;
--sdb-13-mut: #5c5c66;
--sdb-13-acc: #0f5c8c;
--sdb-13-acc: oklch(45% 0.1 232);
--sdb-13-acc-soft: #dceaf4;
}
}
@media (prefers-color-scheme: light) {
.sdb-13:has(.sdb-13__theme-input:checked) {
--sdb-13-bg: #0d0d0f;
--sdb-13-surf: #15151a;
--sdb-13-surf2: #1d1d23;
--sdb-13-line: #2a2a31;
--sdb-13-ink: #eeeef1;
--sdb-13-mut: #9a9aa4;
--sdb-13-acc: #6fbde8;
--sdb-13-acc: oklch(78% 0.1 232);
--sdb-13-acc-soft: #16283a;
}
}
.sdb-13__theme {
position: absolute;
inset-block-start: 0.85rem;
inset-inline-end: 0.85rem;
z-index: 60;
}
.sdb-13__theme-input {
position: absolute;
inset: 0;
inline-size: 100%;
block-size: 100%;
margin: 0;
opacity: 0;
cursor: pointer;
}
.sdb-13__theme-label {
display: grid;
place-items: center;
inline-size: 2.75rem;
block-size: 2.75rem;
border: 1px solid var(--sdb-13-line);
background: var(--sdb-13-surf);
color: var(--sdb-13-ink);
cursor: pointer;
}
.sdb-13__theme-label svg {
inline-size: 1.15rem;
block-size: 1.15rem;
}
.sdb-13__theme-input:focus-visible + .sdb-13__theme-label {
outline: 2px solid var(--sdb-13-acc);
outline-offset: 2px;
}
.sdb-13__sr {
position: absolute;
inline-size: 1px;
block-size: 1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
}
.sdb-13__app {
display: flex;
min-inline-size: 0;
min-block-size: 100vh;
background: var(--sdb-13-surf);
}
.sdb-13__rail {
flex: none;
inline-size: var(--sdb-13-w);
min-inline-size: 0;
border-inline-end: 1px solid var(--sdb-13-line);
background: var(--sdb-13-surf2);
}
.sdb-13__list {
list-style: none;
margin: 0;
padding: 0.3rem;
display: grid;
gap: 0.2rem;
}
.sdb-13__link {
position: relative;
display: grid;
place-items: center;
min-block-size: 2.75rem;
block-size: 2.75rem;
color: var(--sdb-13-mut);
text-decoration: none;
}
.sdb-13__link svg {
inline-size: 1.3rem;
block-size: 1.3rem;
}
.sdb-13__link:hover {
background: var(--sdb-13-surf);
color: var(--sdb-13-ink);
}
.sdb-13__link:focus-visible {
outline: 2px solid var(--sdb-13-acc);
outline-offset: -2px;
}
.sdb-13__link[aria-current="page"] {
background: var(--sdb-13-acc-soft);
color: var(--sdb-13-acc);
box-shadow: inset 3px 0 0 var(--sdb-13-acc);
}
.sdb-13__tip {
position: absolute;
inset-inline-start: calc(100% + 0.55rem);
inset-block-start: 50%;
transform: translateY(-50%);
z-index: 8;
white-space: nowrap;
pointer-events: none;
padding: 0.3rem 0.55rem;
background: var(--sdb-13-ink);
color: var(--sdb-13-bg);
font-family: var(--sdb-13-font-display);
font-size: 0.8rem;
letter-spacing: 0.04em;
text-transform: uppercase;
opacity: 0;
visibility: hidden;
transition: opacity 120ms ease, visibility 120ms ease;
}
.sdb-13__link:hover .sdb-13__tip,
.sdb-13__link:focus-visible .sdb-13__tip {
opacity: 1;
visibility: visible;
}
.sdb-13__main {
flex: 1;
min-inline-size: 0;
padding: 1.4rem 1.25rem 2.5rem;
display: grid;
gap: 1.1rem;
align-content: start;
}
@media (min-width: 46rem) {
.sdb-13__main {
padding: 2.2rem 2rem 3rem;
}
}
.sdb-13__main-head {
min-inline-size: 0;
}
.sdb-13__main-h {
margin: 0 0 0.2rem;
font-family: var(--sdb-13-font-display);
font-size: clamp(1.5rem, 3.6vw, 2rem);
text-transform: uppercase;
letter-spacing: 0.02em;
}
.sdb-13__main-m {
margin: 0;
font-size: 0.84rem;
color: var(--sdb-13-mut);
font-variant-numeric: tabular-nums;
}
.sdb-13__grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(7rem, 1fr));
gap: 0.5rem;
}
.sdb-13__grid span {
aspect-ratio: 4 / 3;
background: var(--sdb-13-surf2);
border: 1px solid var(--sdb-13-line);
}
.sdb-13__grid span:nth-child(3n) {
background: var(--sdb-13-acc-soft);
}
@media (prefers-reduced-motion: reduce) {
.sdb-13__tip {
transition: none;
}
}.sdb-13 {
--sdb-13-bg: #f4f4f5;
--sdb-13-surf: #ffffff;
--sdb-13-surf2: #e9e9ec;
--sdb-13-line: #d3d3d8;
--sdb-13-ink: #131316;
--sdb-13-mut: #5c5c66;
--sdb-13-acc: #0f5c8c;
--sdb-13-acc: oklch(45% 0.1 232);
--sdb-13-acc-soft: #dceaf4;
--sdb-13-w: 4rem;
--sdb-13-font-display: "Roboto Condensed", "Archivo Narrow", "Helvetica Neue", ui-sans-serif, system-ui, sans-serif;
width: 100%;
min-height: 100vh;
display: block;
position: relative;
box-sizing: border-box;
background: var(--sdb-13-bg);
color: var(--sdb-13-ink);
font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
line-height: 1.55;
}
.sdb-13 *,
.sdb-13 *::before,
.sdb-13 *::after {
box-sizing: border-box;
}
.sdb-13:has(.sdb-13__theme-input:checked) {
--sdb-13-bg: #0d0d0f;
--sdb-13-surf: #15151a;
--sdb-13-surf2: #1d1d23;
--sdb-13-line: #2a2a31;
--sdb-13-ink: #eeeef1;
--sdb-13-mut: #9a9aa4;
--sdb-13-acc: #6fbde8;
--sdb-13-acc: oklch(78% 0.1 232);
--sdb-13-acc-soft: #16283a;
}
@media (prefers-color-scheme: dark) {
.sdb-13 {
--sdb-13-bg: #0d0d0f;
--sdb-13-surf: #15151a;
--sdb-13-surf2: #1d1d23;
--sdb-13-line: #2a2a31;
--sdb-13-ink: #eeeef1;
--sdb-13-mut: #9a9aa4;
--sdb-13-acc: #6fbde8;
--sdb-13-acc: oklch(78% 0.1 232);
--sdb-13-acc-soft: #16283a;
}
.sdb-13:has(.sdb-13__theme-input:checked) {
--sdb-13-bg: #f4f4f5;
--sdb-13-surf: #ffffff;
--sdb-13-surf2: #e9e9ec;
--sdb-13-line: #d3d3d8;
--sdb-13-ink: #131316;
--sdb-13-mut: #5c5c66;
--sdb-13-acc: #0f5c8c;
--sdb-13-acc: oklch(45% 0.1 232);
--sdb-13-acc-soft: #dceaf4;
}
}
@media (prefers-color-scheme: light) {
.sdb-13:has(.sdb-13__theme-input:checked) {
--sdb-13-bg: #0d0d0f;
--sdb-13-surf: #15151a;
--sdb-13-surf2: #1d1d23;
--sdb-13-line: #2a2a31;
--sdb-13-ink: #eeeef1;
--sdb-13-mut: #9a9aa4;
--sdb-13-acc: #6fbde8;
--sdb-13-acc: oklch(78% 0.1 232);
--sdb-13-acc-soft: #16283a;
}
}
.sdb-13__theme {
position: absolute;
inset-block-start: 0.85rem;
inset-inline-end: 0.85rem;
z-index: 60;
}
.sdb-13__theme-input {
position: absolute;
inset: 0;
inline-size: 100%;
block-size: 100%;
margin: 0;
opacity: 0;
cursor: pointer;
}
.sdb-13__theme-label {
display: grid;
place-items: center;
inline-size: 2.75rem;
block-size: 2.75rem;
border: 1px solid var(--sdb-13-line);
background: var(--sdb-13-surf);
color: var(--sdb-13-ink);
cursor: pointer;
}
.sdb-13__theme-label svg {
inline-size: 1.15rem;
block-size: 1.15rem;
}
.sdb-13__theme-input:focus-visible + .sdb-13__theme-label {
outline: 2px solid var(--sdb-13-acc);
outline-offset: 2px;
}
.sdb-13__sr {
position: absolute;
inline-size: 1px;
block-size: 1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
}
.sdb-13__app {
display: flex;
min-inline-size: 0;
min-block-size: 100vh;
background: var(--sdb-13-surf);
}
.sdb-13__rail {
flex: none;
inline-size: var(--sdb-13-w);
min-inline-size: 0;
border-inline-end: 1px solid var(--sdb-13-line);
background: var(--sdb-13-surf2);
}
.sdb-13__list {
list-style: none;
margin: 0;
padding: 0.3rem;
display: grid;
gap: 0.2rem;
}
.sdb-13__link {
position: relative;
display: grid;
place-items: center;
min-block-size: 2.75rem;
block-size: 2.75rem;
color: var(--sdb-13-mut);
text-decoration: none;
}
.sdb-13__link svg {
inline-size: 1.3rem;
block-size: 1.3rem;
}
.sdb-13__link:hover {
background: var(--sdb-13-surf);
color: var(--sdb-13-ink);
}
.sdb-13__link:focus-visible {
outline: 2px solid var(--sdb-13-acc);
outline-offset: -2px;
}
.sdb-13__link[aria-current="page"] {
background: var(--sdb-13-acc-soft);
color: var(--sdb-13-acc);
box-shadow: inset 3px 0 0 var(--sdb-13-acc);
}
.sdb-13__tip {
position: absolute;
inset-inline-start: calc(100% + 0.55rem);
inset-block-start: 50%;
transform: translateY(-50%);
z-index: 8;
white-space: nowrap;
pointer-events: none;
padding: 0.3rem 0.55rem;
background: var(--sdb-13-ink);
color: var(--sdb-13-bg);
font-family: var(--sdb-13-font-display);
font-size: 0.8rem;
letter-spacing: 0.04em;
text-transform: uppercase;
opacity: 0;
visibility: hidden;
transition: opacity 120ms ease, visibility 120ms ease;
}
.sdb-13__link:hover .sdb-13__tip,
.sdb-13__link:focus-visible .sdb-13__tip {
opacity: 1;
visibility: visible;
}
.sdb-13__main {
flex: 1;
min-inline-size: 0;
padding: 1.4rem 1.25rem 2.5rem;
display: grid;
gap: 1.1rem;
align-content: start;
}
@media (min-width: 46rem) {
.sdb-13__main {
padding: 2.2rem 2rem 3rem;
}
}
.sdb-13__main-head {
min-inline-size: 0;
}
.sdb-13__main-h {
margin: 0 0 0.2rem;
font-family: var(--sdb-13-font-display);
font-size: clamp(1.5rem, 3.6vw, 2rem);
text-transform: uppercase;
letter-spacing: 0.02em;
}
.sdb-13__main-m {
margin: 0;
font-size: 0.84rem;
color: var(--sdb-13-mut);
font-variant-numeric: tabular-nums;
}
.sdb-13__grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(7rem, 1fr));
gap: 0.5rem;
}
.sdb-13__grid span {
aspect-ratio: 4 / 3;
background: var(--sdb-13-surf2);
border: 1px solid var(--sdb-13-line);
}
.sdb-13__grid span:nth-child(3n) {
background: var(--sdb-13-acc-soft);
}
@media (prefers-reduced-motion: reduce) {
.sdb-13__tip {
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: Icon Rail Sidebar with Accessible Labels
Source: https://codefronts.com/navigation/css-sidebar-navigation/light-flexible-css-vertical-rail-menu-collapsible/
An icon-only rail where every item keeps an accessible name, and the tooltip is deliberately not that name. The two jobs are separate: the name is permanent clipped text that assistive technology always reads; the tooltip is a visual affordance for anyone who does not recognise the glyph. title is wrong for either — announced inconsistently, absent on touch, and its tooltip cannot be hovered or dismissed.
## HTML
```html
<section class="sdb-13" aria-label="Aperture media library">
<div class="sdb-13__theme">
<input class="sdb-13__theme-input" type="checkbox" id="sdb-13-theme">
<label class="sdb-13__theme-label" for="sdb-13-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-13__sr">Toggle dark theme</span>
</label>
</div>
<div class="sdb-13__app">
<nav class="sdb-13__rail" aria-label="Aperture library">
<ul class="sdb-13__list">
<li><a class="sdb-13__link" href="#sdb-13-main" aria-current="page">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><rect x="4" y="5" width="16" height="14" rx="2" fill="none" stroke="currentColor" stroke-width="1.7"/><path d="M4 15l4-4 4 4 3-3 5 5" fill="none" stroke="currentColor" stroke-width="1.7"/></svg>
<span class="sdb-13__sr">All media</span><span class="sdb-13__tip" aria-hidden="true">All media</span></a></li>
<li><a class="sdb-13__link" href="#sdb-13-main">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M4 7h6l2 2h8v9H4Z" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"/></svg>
<span class="sdb-13__sr">Collections</span><span class="sdb-13__tip" aria-hidden="true">Collections</span></a></li>
<li><a class="sdb-13__link" href="#sdb-13-main">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="11" cy="11" r="6" fill="none" stroke="currentColor" stroke-width="1.7"/><path d="M15.5 15.5L20 20" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/></svg>
<span class="sdb-13__sr">Saved searches</span><span class="sdb-13__tip" aria-hidden="true">Saved searches</span></a></li>
<li><a class="sdb-13__link" href="#sdb-13-main">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M12 4l7 4v8l-7 4-7-4V8Z" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"/></svg>
<span class="sdb-13__sr">Renditions</span><span class="sdb-13__tip" aria-hidden="true">Renditions</span></a></li>
<li><a class="sdb-13__link" href="#sdb-13-main">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M5 19V5h9l5 5v9Z" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"/><path d="M9 13h6" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/></svg>
<span class="sdb-13__sr">Licences</span><span class="sdb-13__tip" aria-hidden="true">Licences</span></a></li>
<li><a class="sdb-13__link" href="#sdb-13-main">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M6 4h12v16l-6-4-6 4Z" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"/></svg>
<span class="sdb-13__sr">Archive</span><span class="sdb-13__tip" aria-hidden="true">Archive</span></a></li>
</ul>
</nav>
<main class="sdb-13__main" id="sdb-13-main">
<div class="sdb-13__main-head">
<h2 class="sdb-13__main-h">All media</h2>
<p class="sdb-13__main-m">1,842 assets · 41 GB · last ingest 14:02</p>
</div>
<div class="sdb-13__grid" aria-hidden="true">
<span></span><span></span><span></span><span></span><span></span><span></span>
<span></span><span></span><span></span><span></span><span></span><span></span>
<span></span><span></span><span></span><span></span><span></span><span></span>
</div>
</main>
</div>
</section>
```
## CSS
```css
.sdb-13 {
--sdb-13-bg: #f4f4f5;
--sdb-13-surf: #ffffff;
--sdb-13-surf2: #e9e9ec;
--sdb-13-line: #d3d3d8;
--sdb-13-ink: #131316;
--sdb-13-mut: #5c5c66;
--sdb-13-acc: #0f5c8c;
--sdb-13-acc: oklch(45% 0.1 232);
--sdb-13-acc-soft: #dceaf4;
--sdb-13-w: 4rem;
--sdb-13-font-display: "Roboto Condensed", "Archivo Narrow", "Helvetica Neue", ui-sans-serif, system-ui, sans-serif;
width: 100%;
min-height: 100vh;
display: block;
position: relative;
box-sizing: border-box;
background: var(--sdb-13-bg);
color: var(--sdb-13-ink);
font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
line-height: 1.55;
}
.sdb-13 *,
.sdb-13 *::before,
.sdb-13 *::after {
box-sizing: border-box;
}
.sdb-13:has(.sdb-13__theme-input:checked) {
--sdb-13-bg: #0d0d0f;
--sdb-13-surf: #15151a;
--sdb-13-surf2: #1d1d23;
--sdb-13-line: #2a2a31;
--sdb-13-ink: #eeeef1;
--sdb-13-mut: #9a9aa4;
--sdb-13-acc: #6fbde8;
--sdb-13-acc: oklch(78% 0.1 232);
--sdb-13-acc-soft: #16283a;
}
@media (prefers-color-scheme: dark) {
.sdb-13 {
--sdb-13-bg: #0d0d0f;
--sdb-13-surf: #15151a;
--sdb-13-surf2: #1d1d23;
--sdb-13-line: #2a2a31;
--sdb-13-ink: #eeeef1;
--sdb-13-mut: #9a9aa4;
--sdb-13-acc: #6fbde8;
--sdb-13-acc: oklch(78% 0.1 232);
--sdb-13-acc-soft: #16283a;
}
.sdb-13:has(.sdb-13__theme-input:checked) {
--sdb-13-bg: #f4f4f5;
--sdb-13-surf: #ffffff;
--sdb-13-surf2: #e9e9ec;
--sdb-13-line: #d3d3d8;
--sdb-13-ink: #131316;
--sdb-13-mut: #5c5c66;
--sdb-13-acc: #0f5c8c;
--sdb-13-acc: oklch(45% 0.1 232);
--sdb-13-acc-soft: #dceaf4;
}
}
@media (prefers-color-scheme: light) {
.sdb-13:has(.sdb-13__theme-input:checked) {
--sdb-13-bg: #0d0d0f;
--sdb-13-surf: #15151a;
--sdb-13-surf2: #1d1d23;
--sdb-13-line: #2a2a31;
--sdb-13-ink: #eeeef1;
--sdb-13-mut: #9a9aa4;
--sdb-13-acc: #6fbde8;
--sdb-13-acc: oklch(78% 0.1 232);
--sdb-13-acc-soft: #16283a;
}
}
.sdb-13__theme {
position: absolute;
inset-block-start: 0.85rem;
inset-inline-end: 0.85rem;
z-index: 60;
}
.sdb-13__theme-input {
position: absolute;
inset: 0;
inline-size: 100%;
block-size: 100%;
margin: 0;
opacity: 0;
cursor: pointer;
}
.sdb-13__theme-label {
display: grid;
place-items: center;
inline-size: 2.75rem;
block-size: 2.75rem;
border: 1px solid var(--sdb-13-line);
background: var(--sdb-13-surf);
color: var(--sdb-13-ink);
cursor: pointer;
}
.sdb-13__theme-label svg {
inline-size: 1.15rem;
block-size: 1.15rem;
}
.sdb-13__theme-input:focus-visible + .sdb-13__theme-label {
outline: 2px solid var(--sdb-13-acc);
outline-offset: 2px;
}
.sdb-13__sr {
position: absolute;
inline-size: 1px;
block-size: 1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
}
.sdb-13__app {
display: flex;
min-inline-size: 0;
min-block-size: 100vh;
background: var(--sdb-13-surf);
}
.sdb-13__rail {
flex: none;
inline-size: var(--sdb-13-w);
min-inline-size: 0;
border-inline-end: 1px solid var(--sdb-13-line);
background: var(--sdb-13-surf2);
}
.sdb-13__list {
list-style: none;
margin: 0;
padding: 0.3rem;
display: grid;
gap: 0.2rem;
}
.sdb-13__link {
position: relative;
display: grid;
place-items: center;
min-block-size: 2.75rem;
block-size: 2.75rem;
color: var(--sdb-13-mut);
text-decoration: none;
}
.sdb-13__link svg {
inline-size: 1.3rem;
block-size: 1.3rem;
}
.sdb-13__link:hover {
background: var(--sdb-13-surf);
color: var(--sdb-13-ink);
}
.sdb-13__link:focus-visible {
outline: 2px solid var(--sdb-13-acc);
outline-offset: -2px;
}
.sdb-13__link[aria-current="page"] {
background: var(--sdb-13-acc-soft);
color: var(--sdb-13-acc);
box-shadow: inset 3px 0 0 var(--sdb-13-acc);
}
.sdb-13__tip {
position: absolute;
inset-inline-start: calc(100% + 0.55rem);
inset-block-start: 50%;
transform: translateY(-50%);
z-index: 8;
white-space: nowrap;
pointer-events: none;
padding: 0.3rem 0.55rem;
background: var(--sdb-13-ink);
color: var(--sdb-13-bg);
font-family: var(--sdb-13-font-display);
font-size: 0.8rem;
letter-spacing: 0.04em;
text-transform: uppercase;
opacity: 0;
visibility: hidden;
transition: opacity 120ms ease, visibility 120ms ease;
}
.sdb-13__link:hover .sdb-13__tip,
.sdb-13__link:focus-visible .sdb-13__tip {
opacity: 1;
visibility: visible;
}
.sdb-13__main {
flex: 1;
min-inline-size: 0;
padding: 1.4rem 1.25rem 2.5rem;
display: grid;
gap: 1.1rem;
align-content: start;
}
@media (min-width: 46rem) {
.sdb-13__main {
padding: 2.2rem 2rem 3rem;
}
}
.sdb-13__main-head {
min-inline-size: 0;
}
.sdb-13__main-h {
margin: 0 0 0.2rem;
font-family: var(--sdb-13-font-display);
font-size: clamp(1.5rem, 3.6vw, 2rem);
text-transform: uppercase;
letter-spacing: 0.02em;
}
.sdb-13__main-m {
margin: 0;
font-size: 0.84rem;
color: var(--sdb-13-mut);
font-variant-numeric: tabular-nums;
}
.sdb-13__grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(7rem, 1fr));
gap: 0.5rem;
}
.sdb-13__grid span {
aspect-ratio: 4 / 3;
background: var(--sdb-13-surf2);
border: 1px solid var(--sdb-13-line);
}
.sdb-13__grid span:nth-child(3n) {
background: var(--sdb-13-acc-soft);
}
@media (prefers-reduced-motion: reduce) {
.sdb-13__tip {
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: Icon Rail Sidebar with Accessible Labels
Source: https://codefronts.com/navigation/css-sidebar-navigation/light-flexible-css-vertical-rail-menu-collapsible/
An icon-only rail where every item keeps an accessible name, and the tooltip is deliberately not that name. The two jobs are separate: the name is permanent clipped text that assistive technology always reads; the tooltip is a visual affordance for anyone who does not recognise the glyph. title is wrong for either — announced inconsistently, absent on touch, and its tooltip cannot be hovered or dismissed.
## HTML
```html
<section class="sdb-13" aria-label="Aperture media library">
<div class="sdb-13__theme">
<input class="sdb-13__theme-input" type="checkbox" id="sdb-13-theme">
<label class="sdb-13__theme-label" for="sdb-13-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-13__sr">Toggle dark theme</span>
</label>
</div>
<div class="sdb-13__app">
<nav class="sdb-13__rail" aria-label="Aperture library">
<ul class="sdb-13__list">
<li><a class="sdb-13__link" href="#sdb-13-main" aria-current="page">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><rect x="4" y="5" width="16" height="14" rx="2" fill="none" stroke="currentColor" stroke-width="1.7"/><path d="M4 15l4-4 4 4 3-3 5 5" fill="none" stroke="currentColor" stroke-width="1.7"/></svg>
<span class="sdb-13__sr">All media</span><span class="sdb-13__tip" aria-hidden="true">All media</span></a></li>
<li><a class="sdb-13__link" href="#sdb-13-main">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M4 7h6l2 2h8v9H4Z" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"/></svg>
<span class="sdb-13__sr">Collections</span><span class="sdb-13__tip" aria-hidden="true">Collections</span></a></li>
<li><a class="sdb-13__link" href="#sdb-13-main">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="11" cy="11" r="6" fill="none" stroke="currentColor" stroke-width="1.7"/><path d="M15.5 15.5L20 20" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/></svg>
<span class="sdb-13__sr">Saved searches</span><span class="sdb-13__tip" aria-hidden="true">Saved searches</span></a></li>
<li><a class="sdb-13__link" href="#sdb-13-main">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M12 4l7 4v8l-7 4-7-4V8Z" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"/></svg>
<span class="sdb-13__sr">Renditions</span><span class="sdb-13__tip" aria-hidden="true">Renditions</span></a></li>
<li><a class="sdb-13__link" href="#sdb-13-main">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M5 19V5h9l5 5v9Z" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"/><path d="M9 13h6" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/></svg>
<span class="sdb-13__sr">Licences</span><span class="sdb-13__tip" aria-hidden="true">Licences</span></a></li>
<li><a class="sdb-13__link" href="#sdb-13-main">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M6 4h12v16l-6-4-6 4Z" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"/></svg>
<span class="sdb-13__sr">Archive</span><span class="sdb-13__tip" aria-hidden="true">Archive</span></a></li>
</ul>
</nav>
<main class="sdb-13__main" id="sdb-13-main">
<div class="sdb-13__main-head">
<h2 class="sdb-13__main-h">All media</h2>
<p class="sdb-13__main-m">1,842 assets · 41 GB · last ingest 14:02</p>
</div>
<div class="sdb-13__grid" aria-hidden="true">
<span></span><span></span><span></span><span></span><span></span><span></span>
<span></span><span></span><span></span><span></span><span></span><span></span>
<span></span><span></span><span></span><span></span><span></span><span></span>
</div>
</main>
</div>
</section>
```
## CSS
```css
.sdb-13 {
--sdb-13-bg: #f4f4f5;
--sdb-13-surf: #ffffff;
--sdb-13-surf2: #e9e9ec;
--sdb-13-line: #d3d3d8;
--sdb-13-ink: #131316;
--sdb-13-mut: #5c5c66;
--sdb-13-acc: #0f5c8c;
--sdb-13-acc: oklch(45% 0.1 232);
--sdb-13-acc-soft: #dceaf4;
--sdb-13-w: 4rem;
--sdb-13-font-display: "Roboto Condensed", "Archivo Narrow", "Helvetica Neue", ui-sans-serif, system-ui, sans-serif;
width: 100%;
min-height: 100vh;
display: block;
position: relative;
box-sizing: border-box;
background: var(--sdb-13-bg);
color: var(--sdb-13-ink);
font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
line-height: 1.55;
}
.sdb-13 *,
.sdb-13 *::before,
.sdb-13 *::after {
box-sizing: border-box;
}
.sdb-13:has(.sdb-13__theme-input:checked) {
--sdb-13-bg: #0d0d0f;
--sdb-13-surf: #15151a;
--sdb-13-surf2: #1d1d23;
--sdb-13-line: #2a2a31;
--sdb-13-ink: #eeeef1;
--sdb-13-mut: #9a9aa4;
--sdb-13-acc: #6fbde8;
--sdb-13-acc: oklch(78% 0.1 232);
--sdb-13-acc-soft: #16283a;
}
@media (prefers-color-scheme: dark) {
.sdb-13 {
--sdb-13-bg: #0d0d0f;
--sdb-13-surf: #15151a;
--sdb-13-surf2: #1d1d23;
--sdb-13-line: #2a2a31;
--sdb-13-ink: #eeeef1;
--sdb-13-mut: #9a9aa4;
--sdb-13-acc: #6fbde8;
--sdb-13-acc: oklch(78% 0.1 232);
--sdb-13-acc-soft: #16283a;
}
.sdb-13:has(.sdb-13__theme-input:checked) {
--sdb-13-bg: #f4f4f5;
--sdb-13-surf: #ffffff;
--sdb-13-surf2: #e9e9ec;
--sdb-13-line: #d3d3d8;
--sdb-13-ink: #131316;
--sdb-13-mut: #5c5c66;
--sdb-13-acc: #0f5c8c;
--sdb-13-acc: oklch(45% 0.1 232);
--sdb-13-acc-soft: #dceaf4;
}
}
@media (prefers-color-scheme: light) {
.sdb-13:has(.sdb-13__theme-input:checked) {
--sdb-13-bg: #0d0d0f;
--sdb-13-surf: #15151a;
--sdb-13-surf2: #1d1d23;
--sdb-13-line: #2a2a31;
--sdb-13-ink: #eeeef1;
--sdb-13-mut: #9a9aa4;
--sdb-13-acc: #6fbde8;
--sdb-13-acc: oklch(78% 0.1 232);
--sdb-13-acc-soft: #16283a;
}
}
.sdb-13__theme {
position: absolute;
inset-block-start: 0.85rem;
inset-inline-end: 0.85rem;
z-index: 60;
}
.sdb-13__theme-input {
position: absolute;
inset: 0;
inline-size: 100%;
block-size: 100%;
margin: 0;
opacity: 0;
cursor: pointer;
}
.sdb-13__theme-label {
display: grid;
place-items: center;
inline-size: 2.75rem;
block-size: 2.75rem;
border: 1px solid var(--sdb-13-line);
background: var(--sdb-13-surf);
color: var(--sdb-13-ink);
cursor: pointer;
}
.sdb-13__theme-label svg {
inline-size: 1.15rem;
block-size: 1.15rem;
}
.sdb-13__theme-input:focus-visible + .sdb-13__theme-label {
outline: 2px solid var(--sdb-13-acc);
outline-offset: 2px;
}
.sdb-13__sr {
position: absolute;
inline-size: 1px;
block-size: 1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
}
.sdb-13__app {
display: flex;
min-inline-size: 0;
min-block-size: 100vh;
background: var(--sdb-13-surf);
}
.sdb-13__rail {
flex: none;
inline-size: var(--sdb-13-w);
min-inline-size: 0;
border-inline-end: 1px solid var(--sdb-13-line);
background: var(--sdb-13-surf2);
}
.sdb-13__list {
list-style: none;
margin: 0;
padding: 0.3rem;
display: grid;
gap: 0.2rem;
}
.sdb-13__link {
position: relative;
display: grid;
place-items: center;
min-block-size: 2.75rem;
block-size: 2.75rem;
color: var(--sdb-13-mut);
text-decoration: none;
}
.sdb-13__link svg {
inline-size: 1.3rem;
block-size: 1.3rem;
}
.sdb-13__link:hover {
background: var(--sdb-13-surf);
color: var(--sdb-13-ink);
}
.sdb-13__link:focus-visible {
outline: 2px solid var(--sdb-13-acc);
outline-offset: -2px;
}
.sdb-13__link[aria-current="page"] {
background: var(--sdb-13-acc-soft);
color: var(--sdb-13-acc);
box-shadow: inset 3px 0 0 var(--sdb-13-acc);
}
.sdb-13__tip {
position: absolute;
inset-inline-start: calc(100% + 0.55rem);
inset-block-start: 50%;
transform: translateY(-50%);
z-index: 8;
white-space: nowrap;
pointer-events: none;
padding: 0.3rem 0.55rem;
background: var(--sdb-13-ink);
color: var(--sdb-13-bg);
font-family: var(--sdb-13-font-display);
font-size: 0.8rem;
letter-spacing: 0.04em;
text-transform: uppercase;
opacity: 0;
visibility: hidden;
transition: opacity 120ms ease, visibility 120ms ease;
}
.sdb-13__link:hover .sdb-13__tip,
.sdb-13__link:focus-visible .sdb-13__tip {
opacity: 1;
visibility: visible;
}
.sdb-13__main {
flex: 1;
min-inline-size: 0;
padding: 1.4rem 1.25rem 2.5rem;
display: grid;
gap: 1.1rem;
align-content: start;
}
@media (min-width: 46rem) {
.sdb-13__main {
padding: 2.2rem 2rem 3rem;
}
}
.sdb-13__main-head {
min-inline-size: 0;
}
.sdb-13__main-h {
margin: 0 0 0.2rem;
font-family: var(--sdb-13-font-display);
font-size: clamp(1.5rem, 3.6vw, 2rem);
text-transform: uppercase;
letter-spacing: 0.02em;
}
.sdb-13__main-m {
margin: 0;
font-size: 0.84rem;
color: var(--sdb-13-mut);
font-variant-numeric: tabular-nums;
}
.sdb-13__grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(7rem, 1fr));
gap: 0.5rem;
}
.sdb-13__grid span {
aspect-ratio: 4 / 3;
background: var(--sdb-13-surf2);
border: 1px solid var(--sdb-13-line);
}
.sdb-13__grid span:nth-child(3n) {
background: var(--sdb-13-acc-soft);
}
@media (prefers-reduced-motion: reduce) {
.sdb-13__tip {
transition: none;
}
}
```How this works
An icon has no text, so the name has to come from somewhere and there are only three candidates. Visually hidden text inside the link is the most robust: it is real content, it is always in the accessibility tree, and it survives every visual state. aria-label is the second, fine but invisible to translation tooling and easy to leave stale. title is the third and it should not be used — it is the last fallback in the name computation, announcement varies by screen reader, and on touch it never surfaces at all.
The tooltip is a different feature with different requirements. It exists for sighted users who cannot identify a glyph, and SC 1.4.13 puts three conditions on it: dismissible without moving the pointer, hoverable so the pointer can move onto it, and persistent until dismissed or the trigger loses focus. A title tooltip fails all three. A CSS tooltip anchored to the item's hover region passes hoverable and persistent, which is most of the way there.
Because the tooltip duplicates the accessible name, it must be hidden from assistive technology. It carries aria-hidden="true"; without that, a screen-reader user hears the label twice — once from the clipped text, once from the tooltip — which is the usual result of bolting a tooltip onto an already-named control. One name, one visual affordance, no overlap.
The trap on a narrow rail is the target size. A 64px rail invites 32px rows, which puts every item under the 44x44 minimum in SC 2.5.8 while looking perfectly neat. Keep the row at 44px minimum regardless of the rail width. The pure-CSS limit worth stating: dismissing the tooltip with Esc while the pointer stays put needs a key handler, so a strict 1.4.13 implementation adds a few lines of script — css-side-menu covers the visual variants of this rail; the naming contract is what matters here.
Make it yours
- Change the rail width with
--sdb-13-w; keep the row at 44px minimum whatever you choose. - Move the tooltip to the other side by flipping
inset-inline-starttoinset-inline-endfor a right-hand rail. - Replace the clipped label with
aria-labelif your build pipeline strips hidden text; keep exactly one of the two. - Add a delay before the tooltip appears with
transition-delayso a fast pointer sweep does not flash five tooltips. - Expand the rail on hover into a full-width overlay if you want labels visible without a tooltip — the accessible name does not change.
- Add an Esc handler to dismiss the tooltip without moving the pointer, which is the one part of SC 1.4.13 that CSS alone cannot reach.
Gotchas — read before shipping
titleas the accessible name is announced inconsistently, never appears on touch, and produces a tooltip that cannot be hovered or dismissed — three problems in one attribute.- A tooltip that repeats the accessible name without
aria-hiddenmakes every item announce twice. - Hiding the label with
display: noneinstead of clipping it removes the name entirely, leaving a rail of links called "link". - A 32px row looks right in a 64px rail and fails SC 2.5.8. Target size is independent of rail width.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 15.4+ | 121+ | 111+ |
oklch() and the :has() theme toggle set the floor; the rail, the clipped labels and the CSS tooltip are flexbox and :hover / :focus-visible, Baseline since 2020. Engines below the oklch() floor take the sRGB hex declared first on every token, and the accessible names are unaffected by any of it because they are real text in the DOM.