LEARNING OBJECTIVES ⌵
- Construct multi-level hierarchical navigation trees using native semantic HTML5
<details>and<summary>elements. - Implement ARIA active state semantics using
aria-current="page"andaria-expanded. - Support complete keyboard navigation: Enter, Space, ArrowDown, ArrowUp, and focus restoration.
- Persist sidebar accordion collapse/expansion states in
localStorageacross page navigations.
📖 The Mental Model & Story (Intuitive Foundation)
Imagine browsing through a deep 500-page API documentation manual with dozens of nested modules, classes, methods, and tutorials. If every single item is expanded all at once, you face a terrifying "wall of links" spanning 10 screens of vertical scrolling.
Conversely, if everything is collapsed behind custom JavaScript <div> buttons that do not support native keyboard shortcuts or screen readers, the documentation becomes unusable for non-mouse users.
The mental model of an accessible sidebar is a hierarchical tree of disclosure widgets. In native HTML5, <details> and <summary> are designed exactly for this purpose. They offer zero-JS built-in disclosure, native keyboard interaction (Enter / Space toggles), and accessibility attributes baked right into the browser engine. When enhanced with lightweight state persistence and active link indicators (aria-current="page"), we create a high-performance, rock-solid navigation tree.
Technical Deep Dive & Specifications
2.1 Sidebar Hierarchy & DOM Structure
A modern documentation sidebar consists of category groups containing nested lists of topic links:
+-------------------------------------------------------------+
| <nav aria-label="Documentation Sidebar"> |
| <ul class="nav-tree"> |
| <!-- Group 1: Native HTML5 Details Disclosure --> |
| <li> |
| <details open data-group-id="getting-started"> |
| <summary> |
| <span class="folder-icon">📁</span> |
| <span class="group-title">Getting Started</span> |
| <span class="chevron" aria-hidden="true">▶</span> |
| </summary> |
| <ul class="sub-nav"> |
| <li><a href="/intro">Introduction</a></li> |
| <li><a href="/install">Installation</a></li> |
| <li> |
| <a href="/arch" aria-current="page"> |
| Architecture (Active) |
| </a> |
| </li> |
| </ul> |
| </details> |
| </li> |
| <!-- Group 2: Advanced Topics --> |
| <li> |
| <details data-group-id="advanced"> |
| <summary>...</summary> |
| <ul class="sub-nav">...</ul> |
| </details> |
| </li> |
| </ul> |
| </nav> |
+-------------------------------------------------------------+
2.2 Semantic Requirements & WAI-ARIA Specifications
| HTML / ARIA Attribute | Target Element | Purpose & Accessibility Function |
|---|---|---|
<nav aria-label="..."> |
Root container | Distinguishes the sidebar navigation landmark from header/breadcrumb landmarks. |
<details [open]> |
Accordion group | Native disclosure element; the open boolean attribute determines visibility. |
<summary> |
Group trigger | Native focusable header that toggles the parent <details> on Click, Enter, or Space. |
aria-current="page" |
Active <a> link |
Informs screen reader users that this link matches the currently displayed page. |
aria-hidden="true" |
Chevron icons / SVGs | Prevents decorative visual indicators from cluttering screen reader announcements. |
2.3 Eliminating Summary Markers and Customizing Chevrons
Browsers natively render a disclosure triangle on <summary>. To replace this with a smooth rotating custom SVG chevron:
/* Remove native browser disclosure triangles across engines */
summary {
list-style: none; /* Modern standard */
display: flex;
align-items: center;
justify-content: space-between;
cursor: pointer;
user-select: none;
}
summary::-webkit-details-marker {
display: none; /* Legacy Safari / WebKit */
}
/* Custom animated chevron */
.chevron {
transition: transform 200ms cubic-bezier(0.4, 0, 0.2, 1);
}
details[open] > summary .chevron {
transform: rotate(90deg);
}
2.4 State Persistence Strategy
When navigating between pages in a multi-page documentation portal (MPA), all <details> elements reset to their default HTML state unless synchronized. We persist states using localStorage:
- Assign each
<details>a uniquedata-group-id. - On
toggleevent, write{ [groupId]: details.open }tolocalStorage. - Auto-expand any
<details>containing the active link ([aria-current="page"]) regardless of saved state.
💻 Interactive Code Playground
Starter Code
Line-by-Line Code Breakdown
- Line 33–48: CSS removes default user-agent disclosure triangles via
list-style: noneand::-webkit-details-marker. - Lines 49–54:
.chevronrotates 90 degrees cleanly whendetails[open]attribute is toggled. - Lines 73–77:
.sub-nav a[aria-current="page"]applies prominent visual highlighting to the active page while providing semantic cues for screen readers. - Lines 84–97:
<details open data-group="getting-started">provides native accessible disclosure without custom ARIA hacks. - Lines 135–158: JavaScript restores persisted accordion state from
localStorageon page load, automatically forces open any group containing[aria-current="page"], and updates storage on the nativetoggleevent.
Expected Browser Render Output
+------------------------------------+--------------------------------------------+
| [Documentation Navigation] | # Quickstart Guide |
| ▼ 🚀 Getting Started | |
| | Overview | This active page is highlighted using |
| | [Quickstart Guide (Active)] | aria-current="page". |
| | CLI Installation | |
| | |
| ▼ 🧩 Core Concepts | |
| | Semantic HTML | |
| | ARIA Patterns | |
| | Focus Management | |
| | |
| ▶ ⚡ API Reference | |
+------------------------------------+--------------------------------------------+🏋️ Hands-On Exercise
🎯 The Challenge: Add Keyboard Tree Navigation (Arrow Keys)
Instructions:
- Enhance the sidebar so that when a
<summary>or<a>link has focus, pressing ArrowDown moves focus to the next visible focusable item, and ArrowUp moves to the previous visible item. - Pressing ArrowRight on a collapsed
<summary>must expand it (open = true). - Pressing ArrowLeft on an expanded
<summary>must collapse it (open = false).
🏁 Starter Code Sandbox
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Sidebar Keyboard Challenge</title>
</head>
<body>
<nav class="doc-sidebar" aria-label="Documentation">
<ul class="nav-tree">
<li>
<details open>
<summary>Category A</summary>
<ul>
<li><a href="#a1">Item A.1</a></li>
<li><a href="#a2">Item A.2</a></li>
</ul>
</details>
</li>
<li>