SolutionsCase StudiesAI ToolsResourcesAboutContact
Get Your Free AI Automation Audit Book a Free 30-Minute Strategy Call
Playground~/frontend Open editor

~/css/sidebar-navigation

7 CSS Sidebar Navigation

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.

01. Grouped Sidebar Navigation

SidebarNavigation

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.

View code
<nav class="snv1" aria-label="Settings"><h3 id="snv1a">Account</h3><ul aria-labelledby="snv1a"><li><a href="#profile" aria-current="page">Profile</a></li><li><a href="#security">Security</a></li></ul><h3 id="snv1b">Workspace</h3><ul aria-labelledby="snv1b"><li><a href="#members">Members</a></li><li><a href="#billing">Billing</a></li><li><a href="#integrations">Integrations</a></li></ul></nav>
.snv1 { width: 220px; max-width: 100%; box-sizing: border-box; padding: 12px; border: 1px solid #e4e7ec; border-radius: 14px; background: #fff; font: 14px Inter, system-ui, sans-serif; }
.snv1 h3 { margin: 10px 8px 4px; color: #475467; font-size: 11px; text-transform: uppercase; letter-spacing: .08em; } .snv1 h3:first-child { margin-top: 0; }
.snv1 ul { margin: 0; padding: 0; list-style: none; }
.snv1 a { display: block; padding: 8px; border-radius: 8px; color: #344054; text-decoration: none; }
.snv1 a:hover { background: #f9fafb; }
.snv1 a[aria-current] { background: #f4ebff; color: #53389e; font-weight: 600; }
.snv1 a:focus-visible { outline: 3px solid #d6bbfb; outline-offset: 1px; }

02. Collapsible Sidebar Groups

SidebarDisclosure

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.

View code
<nav class="snv2" aria-label="Docs"><details open><summary>Getting started</summary><a href="#install" aria-current="page">Install</a><a href="#config">Configure</a></details><details><summary>Components</summary><a href="#button">Button</a><a href="#dialog">Dialog</a><a href="#tabs">Tabs</a></details><details><summary>Guides</summary><a href="#theming">Theming</a></details></nav>
.snv2 { width: 230px; max-width: 100%; box-sizing: border-box; padding: 8px; border-radius: 14px; background: #111727; font: 14px Inter, system-ui, sans-serif; }
.snv2 summary { display: flex; justify-content: space-between; align-items: center; padding: 9px 8px; border-radius: 8px; color: #f1f5f9; font-weight: 700; cursor: pointer; list-style: none; }
.snv2 summary::-webkit-details-marker { display: none; }
.snv2 summary::after { content: ""; width: 7px; height: 7px; border-right: 2px solid #64748b; border-bottom: 2px solid #64748b; transform: rotate(-45deg); transition: transform .2s; }
.snv2 details[open] summary::after { transform: rotate(45deg); }
.snv2 a { display: block; margin-left: 10px; padding: 6px 8px 6px 12px; border-left: 1px solid #263048; color: #94a3b8; text-decoration: none; }
.snv2 a:hover { color: #fff; }
.snv2 a[aria-current] { border-left: 2px solid #38bdf8; color: #38bdf8; font-weight: 600; }
.snv2 :focus-visible { outline: 2px solid #38bdf8; outline-offset: 1px; border-radius: 6px; }
@media (prefers-reduced-motion: reduce) { .snv2 summary::after { transition: none; } }

03. Sidebar with Icons and Counts

SidebarBadges

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.

View code
<nav class="snv3" aria-label="Mail"><a href="#inbox" aria-current="page"><span aria-hidden="true">📥</span>Inbox<em>12</em></a><a href="#starred"><span aria-hidden="true">⭐</span>Starred</a><a href="#sent"><span aria-hidden="true">📤</span>Sent</a><a href="#spam"><span aria-hidden="true">🚫</span>Spam<em>3</em></a></nav>
.snv3 { display: grid; gap: 2px; width: 220px; max-width: 100%; font: 14px Inter, system-ui, sans-serif; }
.snv3 a { display: flex; align-items: center; gap: 10px; padding: 9px 10px; border-radius: 10px; color: #344054; text-decoration: none; }
.snv3 a:hover { background: #f2f4f7; }
.snv3 a[aria-current] { background: #101828; color: #fff; font-weight: 600; }
.snv3 em { margin-left: auto; padding: 1px 8px; border-radius: 999px; background: #eaecf0; color: #344054; font-size: 12px; font-style: normal; font-weight: 700; }
.snv3 a[aria-current] em { background: #344054; color: #fff; }
.snv3 a:focus-visible { outline: 3px solid #84adff; outline-offset: 1px; }

04. Active Bar Indicator

SidebarIndicator

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.

View code
<nav class="snv4" aria-label="Project"><a href="#board">Board</a><a href="#timeline" aria-current="page">Timeline</a><a href="#files">Files</a><a href="#settings">Settings</a></nav>
.snv4 { display: grid; width: 200px; max-width: 100%; padding: 8px 0; border-radius: 12px; background: #0b1020; font: 14px Inter, system-ui, sans-serif; }
.snv4 a { position: relative; padding: 10px 18px; color: #94a3b8; text-decoration: none; }
.snv4 a:hover { color: #e2e8f0; background: #111727; }
.snv4 a[aria-current] { color: #fff; font-weight: 600; background: #151b2e; }
.snv4 a[aria-current]::before { content: ""; position: absolute; left: 0; top: 8px; bottom: 8px; width: 3px; border-radius: 0 3px 3px 0; background: #a78bfa; }
.snv4 a:focus-visible { outline: 2px solid #a78bfa; outline-offset: -2px; }

05. Nested Tree Navigation

SidebarTree

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.

View code
<nav class="snv5" aria-label="Files"><ul><li><button aria-expanded="true" aria-controls="snv5a">Marketing</button><ul id="snv5a"><li><a href="#brief">Q4 brief</a></li><li><button aria-expanded="false" aria-controls="snv5b">Campaigns</button><ul id="snv5b" hidden><li><a href="#spring">Spring</a></li><li><a href="#summer">Summer</a></li></ul></li></ul></li><li><button aria-expanded="false" aria-controls="snv5c">Design</button><ul id="snv5c" hidden><li><a href="#logo">Logo files</a></li></ul></li></ul></nav>
.snv5 { width: 240px; max-width: 100%; box-sizing: border-box; padding: 10px; border: 1px solid #e4e7ec; border-radius: 14px; background: #fff; font: 14px Inter, system-ui, sans-serif; }
.snv5 ul { margin: 0; padding: 0; list-style: none; } .snv5 ul ul { margin-left: 14px; padding-left: 8px; border-left: 1px solid #eaecf0; }
.snv5 button { display: flex; align-items: center; gap: 6px; width: 100%; padding: 7px 6px; border: 0; border-radius: 6px; background: none; color: #101828; font: 600 14px Inter, system-ui, sans-serif; text-align: left; cursor: pointer; }
.snv5 button::before { content: "▸"; color: #475467; transition: transform .2s; } .snv5 button[aria-expanded=true]::before { transform: rotate(90deg); }
.snv5 a { display: block; padding: 6px; border-radius: 6px; color: #344054; text-decoration: none; } .snv5 a:hover, .snv5 button:hover { background: #f2f4f7; }
.snv5 :focus-visible { outline: 3px solid #84adff; outline-offset: 1px; }
@media (prefers-reduced-motion: reduce) { .snv5 button::before { transition: none; } }
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.

View code
<div class="snv6"><button aria-haspopup="menu" aria-expanded="false" aria-label="Switch workspace, current: Northwind"><span class="lg" aria-hidden="true">N</span><span class="nm">Northwind<small>Pro plan</small></span><span aria-hidden="true">⌄</span></button><nav aria-label="Workspace"><a href="#home" aria-current="page">Home</a><a href="#projects">Projects</a><a href="#people">People</a></nav></div>
.snv6 { width: 230px; max-width: 100%; box-sizing: border-box; padding: 10px; border-radius: 14px; background: #111727; font: 14px Inter, system-ui, sans-serif; }
.snv6 button { display: flex; align-items: center; gap: 10px; width: 100%; margin-bottom: 10px; padding: 8px; border: 1px solid #263048; border-radius: 10px; background: #151b2e; color: #f1f5f9; text-align: left; cursor: pointer; }
.snv6 .lg { display: grid; place-items: center; width: 32px; height: 32px; border-radius: 8px; background: #f59e0b; color: #1c1917; font-weight: 900; }
.snv6 .nm { flex: 1; display: grid; font-weight: 700; } .snv6 small { color: #94a3b8; font-weight: 500; font-size: 12px; }
.snv6 nav { display: grid; gap: 2px; }
.snv6 a { padding: 8px 10px; border-radius: 8px; color: #94a3b8; text-decoration: none; } .snv6 a:hover { background: #151b2e; color: #e2e8f0; }
.snv6 a[aria-current] { background: #1e2546; color: #fff; }
.snv6 :focus-visible { outline: 2px solid #fbbf24; outline-offset: 2px; }

07. Sidebar with a Filter Field

SidebarSearch

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.

View code
<nav class="snv7" aria-label="API reference"><label for="snv7q" class="sr">Filter pages</label><input id="snv7q" type="search" placeholder="Filter…"><p aria-live="polite" class="n">8 pages</p><ul><li><a href="#auth">Authentication</a></li><li><a href="#orders">Orders</a></li><li><a href="#products">Products</a></li><li><a href="#refunds">Refunds</a></li><li><a href="#customers">Customers</a></li><li><a href="#webhooks">Webhooks</a></li><li><a href="#errors">Errors</a></li><li><a href="#limits">Rate limits</a></li></ul></nav>
.snv7 { width: 220px; max-width: 100%; box-sizing: border-box; padding: 10px; border: 1px solid #e4e7ec; border-radius: 14px; background: #fff; font: 14px Inter, system-ui, sans-serif; }
.snv7 input { width: 100%; box-sizing: border-box; padding: 8px 10px; border: 1px solid #98a2b3; border-radius: 8px; font: inherit; }
.snv7 input:focus-visible { outline: 3px solid #84adff; outline-offset: 1px; }
.snv7 .n { margin: 6px 2px; color: #475467; font-size: 12px; }
.snv7 ul { margin: 0; padding: 0; list-style: none; }
.snv7 a { display: block; padding: 6px 8px; border-radius: 6px; color: #344054; text-decoration: none; } .snv7 a:hover { background: #f2f4f7; }
.snv7 a:focus-visible { outline: 3px solid #84adff; outline-offset: 1px; }
.snv7 .sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }
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.