5 Pure CSS2 with JSPublished 2026-10-05Free · no sign-up
Seven sidebar navigation menus: links grouped under headings, groups that fold away, an inbox sidebar with counts, a subtle active indicator bar, a nested folder tree, an app sidebar with a workspace switcher and a long reference list with a filter field.
A sidebar with links grouped under small headings, the current page marked with aria-current="page" and a background. Each group is a list with an accessible name from its heading via aria-labelledby, so screen reader users hear which section a link is in.
Each group can be folded away with a details element, so long navigation stays manageable. The group containing the current page is open by default, and the summary shows a chevron that turns with the open state.
An inbox-style sidebar with icons and unread counts. Counts are part of each link's text, read as Inbox 12, so they do not need hidden labels; the icons are decorative, and the count pill is pushed to the right with margin-left: auto.
The current link is marked by a coloured bar on the left edge and brighter text, a subtle style for dense apps. The bar is a pseudo-element, and the current state is driven only by aria-current, so the styling always matches what is announced.
Folders that expand to show their pages, with real buttons for expanding and real links for navigating, kept separate so each does one job. Nested lists carry the structure, and each toggle button has aria-expanded and names the folder it opens.
document.querySelectorAll('.snv5 button').forEach(function (b) { b.onclick = function () { var o = b.getAttribute('aria-expanded') !== 'true'; b.setAttribute('aria-expanded', o); document.getElementById(b.getAttribute('aria-controls')).hidden = !o; }; });
06. Workspace Switcher on Top
SidebarApp
An app sidebar headed by a workspace switcher showing the current team, followed by the navigation. The switcher is a button that would open a menu of workspaces; its accessible name says both what it is and which workspace is current.
Long sidebars, such as API references, get a filter field that hides non-matching links as you type and says how many remain. The field is labelled, the count is a polite live region, and nothing is removed from the DOM, only hidden.
var q = document.getElementById('snv7q'), lis = document.querySelectorAll('.snv7 li'), n = document.querySelector('.snv7 .n');
q.oninput = function () { var t = q.value.trim().toLowerCase(), k = 0; lis.forEach(function (li) { var hit = li.textContent.toLowerCase().indexOf(t) !== -1; li.hidden = !hit; if (hit) k++; }); n.textContent = k === 0 ? 'No pages match' : k + (k === 1 ? ' page' : ' pages'); };
About these CSS Sidebar Navigation
Mark the current page
Put aria-current="page" on the link for the page being viewed and style from that attribute. The visual highlight and what assistive technology announces then always agree.
Groups need names
Group headings help everyone scan. Linking each list to its heading with aria-labelledby lets screen reader users hear which group a link belongs to.
Expand and navigate are separate jobs
In a tree, the folder toggle is a button with aria-expanded and the pages are links. Mixing both into one element makes it unclear whether a click opens or navigates.
Filters that report
A filter field over a long list should say how many items remain, in a polite live region, so the result is clear without looking at the list.
Frequently asked questions
nav or aside for a sidebar?
If it is site or app navigation, nav with a label. An aside is for complementary content.
How do I keep the sidebar visible while scrolling?
Make it position: sticky with top: 0 and a max-height with its own scroll.
Should groups start collapsed?
Keep the group with the current page open, and others collapsed if the list is long.