webdevpuneet.com

Nested Sidebar Navigation with Active-Path Auto-Expand — Real grid-template-rows Animation

Nested Sidebar Nav with Active Path Auto-Expand · Navigation · Plain HTML, CSS & JS · Live preview

CategoryNavigation

Copy & paste

Source Code

<div class="demo">
  <nav class="tree-nav" id="treeNav" aria-label="Documentation">
    <div class="tree-group">
      <button class="tree-parent" aria-expanded="false">
        <svg class="chev" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.8" stroke-linecap="round"><path d="m9 6 6 6-6 6"/></svg>
        Getting Started
      </button>
      <div class="tree-children">
        <a href="#" class="tree-link">Installation</a>
        <a href="#" class="tree-link">Quick start</a>
        <a href="#" class="tree-link">Configuration</a>
      </div>
    </div>

    <div class="tree-group">
      <button class="tree-parent" aria-expanded="false">
        <svg class="chev" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.8" stroke-linecap="round"><path d="m9 6 6 6-6 6"/></svg>
        API Reference
      </button>
      <div class="tree-children">
        <a href="#" class="tree-link">Authentication</a>
        <a href="#" class="tree-link active" data-active="true">Rate limits</a>
        <a href="#" class="tree-link">Pagination</a>
        <a href="#" class="tree-link">Webhooks</a>
      </div>
    </div>

    <div class="tree-group">
      <button class="tree-parent" aria-expanded="false">
        <svg class="chev" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.8" stroke-linecap="round"><path d="m9 6 6 6-6 6"/></svg>
        Guides
      </button>
      <div class="tree-children">
        <a href="#" class="tree-link">Migrating from v1</a>
        <a href="#" class="tree-link">Error handling</a>
      </div>
    </div>
  </nav>
</div>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

What's included

Features

Automatically expands exactly the group containing the currently active link on load, nothing else
Active state correctly derived from link to group, not hardcoded per group, so it adapts if the active link changes
Smooth grid-template-rows animation avoids both the max-height guessing problem and the non-animatable display toggle
Overflow-clipping wrapper built programmatically, keeping the authored HTML markup simple and flat
Groups remain fully manually togglable by the user independent of the automatic active-path expansion
aria-expanded kept accurate on every group's toggle button for both automatic and manual state changes
Chevron icon rotation gives clear visual feedback for each group's open/closed state
Reusable expandActivePath() function can be re-invoked after client-side route changes in an SPA

About this UI Snippet

Nested Sidebar Nav — Auto-Expanding Exactly the Active Path, Nothing Else

Screenshot of the Nested Sidebar Nav with Active Path Auto-Expand snippet rendered live

A collapsible sidebar with several top-level groups is only genuinely useful if it opens to the right place automatically — a user landing on a "Rate limits" documentation page shouldn't have to manually click open "API Reference" themselves just to see where they currently are in the site structure. This snippet computes that on load: it finds whichever link is marked active, walks up to its containing group, and expands only that one group, leaving every sibling group collapsed.

Finding the active group from the active link, not the reverse

expandActivePath() starts from nav.querySelector('.tree-link.active') — the link, not the group — and calls .closest('.tree-group') on it to find its containing parent group. This direction matters: the active state genuinely belongs on the *link* (it's the specific page the user is on), and the group's open/closed state is *derived* from that, not the other way around. If a different link becomes active later (e.g. after a client-side route change), re-running the same lookup would correctly find and expand its new containing group without any group-specific logic needing to change.

Smooth open/close via the same grid-template-rows trick used elsewhere in this library

Rather than animating height (which requires a fixed pixel target CSS can't compute from auto) or a hard display toggle (which can't animate at all), .tree-children uses grid-template-rows: 0fr at rest and 1fr when its group has the .open class — the same fractional-grid-row animation technique used by this library's CSS-only accordion, applied here to a JavaScript-driven nested nav instead of a pure-CSS checkbox hack.

Building the required overflow wrapper dynamically, not by hand in the markup

The grid-row-collapse technique needs an inner wrapper with overflow: hidden so content visually clips as the row shrinks toward zero. Rather than requiring that wrapper <div> to be written by hand around every group's links in the HTML, the setup code moves each group's existing child links into a wrapper it creates programmatically on load — keeping the authored HTML markup simpler (flat <a> tags directly inside .tree-children) while still getting the wrapper structure the animation technique actually needs underneath.

Only one group opens automatically — the rest wait for a deliberate click

expandActivePath() only ever calls setGroupOpen(group, true) for the one group containing the active link; every other group is left in its default collapsed state. This keeps the sidebar's initial state focused and uncluttered — showing exactly "here's where you are" rather than expanding everything and asking the user to scan a wall of open sections to find their current location themselves.

Step by step

How to Use

  1. 1
    Mark the current page's link with class="active" and data-active="true"The active link's containing group is automatically found and expanded on load — no manual group-opening needed.
  2. 2
    Click any group header to expand/collapse it manuallyGroups can also be freely opened and closed independently by the user at any time, in addition to the automatic active-path expansion.
  3. 3
    Add or remove nav groups and links freelyAny number of .tree-group blocks with their own .tree-parent and .tree-children are picked up automatically.
  4. 4
    Re-run expandActivePath() after a route changeIn a single-page app, call expandActivePath() again after updating which link has the .active class, so the sidebar stays in sync with client-side navigation.
  5. 5
    Adjust the animation speedChange the grid-template-rows transition duration on .tree-children in the CSS panel.

Real-world uses

Common Use Cases

📚
Documentation site sidebars
Open a doc nav to the right place automatically, expanding only the group that contains the current page's link on load.
🧭
Admin panel navigation
Provide nested settings or resource menus that land on the active section, with aria-expanded kept in step with each group.
🗂️
Multi-level product navigation
Support any product with grouped, collapsible sections, animating height through grid-template-rows without guessing a max-height value.
❓
Help centre and knowledge base sidebars
Help visitors orient themselves, deriving the active group from the link rather than hardcoding it for each group.
Related: Pinned, Draggable Browser-Style Tabs
See the Pinned, Draggable Browser-Style Tabs for a related navigation pattern worth pairing with this one.

Got questions?

Frequently Asked Questions

It looks up whichever link currently has the .active class, then walks up the DOM using .closest('.tree-group') to find that link's containing parent group, and expands only that one group — the active state is read from the link and used to derive the group's state, not the reverse.

Only the one group containing the active link is automatically expanded on load; every other group remains in its default collapsed state until a user manually clicks to open it.

max-height requires guessing a fixed pixel value taller than the tallest possible content, which either clips real content or creates an uneven animation pace; grid-template-rows animating between 0fr and 1fr always sizes to the content's exact real height with no guessing, the same technique used by this library's CSS-only accordion snippets.

It keeps the authored markup simpler — plain <a> links directly inside .tree-children — while still providing the overflow:hidden wrapper the grid-row-collapse animation technique needs underneath, moving that structural detail into the setup code rather than requiring every author to remember to add it by hand.

Update which link has the .active class to match the new current page, then call expandActivePath() again — it will find the new active link's containing group and expand it the same way it did on initial load.

Yes — the automatic active-path expansion only sets the initial state; every group remains fully independently togglable by clicking its header at any time afterward, including the one that was auto-expanded.

Yes. While the Nested Sidebar Nav with Active Path Auto-Expand snippet gives you one pattern to copy, the free Responsive Navbar Builder at webdevpuneet.com/navbar-builder/ designs a whole navigation bar: logo, links and dropdown menus, a mobile hamburger menu (dropdown panel, slide-in drawer or full screen) and a scroll effect (sticky, shrink, hide on scroll or transparent). It has six presets, a live preview that runs the real code, and exports HTML + CSS + vanilla JS, React, Tailwind, Vue 3 or Angular — or you can click Fork & Edit to keep editing it in My Code.