fix(sidebar): highlight nearest ancestor item - #2120
Merged
Conversation
The sidebar markup is rendered once per section and cached, so the active item is resolved client side. Matching only considered links flagged `data-sidebar-match="prefix"`, which the L1-only mode emits and the full-tree mode never does. A page absent from the menu therefore matched nothing at all: no active item, every group collapsed. Pages built with `build.list = never`, or generated by a content adapter, are excluded from the page collections the menu is built from, so they never have an entry of their own and always hit this path. Fall back to the longest link that is a path ancestor of the current page whenever no link matches exactly. Ancestor matching compares against `path + "/"`, so `/docs/guides` cannot claim `/docs/guides-advanced/`. The brand logo is excluded from the candidates: it sits inside the nav and points at the site home, so it would otherwise match every page below it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`data-sidebar-match="prefix"` marked the links that were allowed to match the current page as an ancestor. Ancestor matching is now derived from the href itself and applies to every link, so nothing reads the attribute: not the highlighting script, not the stylesheets, not the docs. Remove the attribute and the `prefixMatch` argument that threaded it through the inline item partial. Rendered markup loses one attribute and is otherwise unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
✅ Deploy Preview for gethinode-demo ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
A link to the site root is an ancestor of every page, so ancestor matching would let a "Home" or "Overview" entry claim any page that is not in the menu. Checked against a consuming app: the sidebar wrongly highlighted Overview on /contact/, /cookies/ and /privacy/, which sit outside the menu entirely and previously highlighted nothing. Skip root links in the ancestor pass. They still win when they match the current path exactly, so the home page itself is unaffected. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Collaborator
Author
|
🎉 This PR is included in version 3.19.2 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Navigating to a page that has no sidebar entry of its own leaves the sidebar completely inert — no active item, no
aria-current, and every group collapsed. Nothing is highlighted at all.Sidebar markup is rendered once per
(section, version, language, variant)and cached, so the active item is resolved client side byassets/js/critical/sidebar-active.js. That matcher only treated a link as an ancestor candidate if it carrieddata-sidebar-match="prefix". Only the L1-only mode (level-min <= 1,level-max == 1) emits that attribute; the default full-tree mode never does. So in the full-tree sidebar there were no ancestor candidates at all, and a page missing from the menu matched nothing.Pages built with
build.list = never, and pages generated by a content adapter, are excluded from the page collectionsassets/live-pages.htmlbuilds the menu from. They can never have an entry of their own, so they always land on this path.Reproduced on a docs site whose CLI reference is generated by a content adapter: on
/docs/reference/command-line/version/the built page had 0 active links, 0aria-current, 0 expanded groups, and 0 prefix-capable links across both nav instances — while the/docs/reference/command-line/link was present in the markup the whole time.Fix
1.
fix(sidebar): highlight nearest ancestor itemExact match still wins outright. Only when no link matches exactly does the longest link that is a path ancestor of the current page take over, flagged
aria-current="true"rather than"page". Ancestor matching compares againstpath + "/", so/docs/guidescannot claim/docs/guides-advanced/.The brand logo is excluded from the candidates. It sits inside the
<nav>and points at the site home, so under ancestor matching it would match every page below it. Verified necessary: without the guard, a page with no ancestor in the menu lights up the logo, and the home page marks it active.2.
refactor(sidebar): drop dead prefix-match markupWith ancestor matching derived from the href, nothing reads
data-sidebar-match="prefix"any more — not the script, not the stylesheets, not the docs. Removes the attribute and theprefixMatchargument threading it through the inline item partial. Rendered markup loses one attribute and is otherwise unchanged.Verification
aria-current, 0 expanded groupsaria-current="true", parent group expanded, link visible — both navsdata-sidebar-matchin built HTML after strippnpm test(eslint, stylelint, markdownlint, template build)Note
There is no standing automated coverage for this: the repo has no JS unit harness (
pnpm testis lint plus a Hugo template build), andtests/visual/has Playwright installed but no specs. Verification above was a one-off browser run over both builds. Happy to wire up atests/visual/spec in a follow-up if that is wanted.🤖 Generated with Claude Code