chore: add theme chrome audit for the navbar redesign

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
navbar-redesign-triage
Divyansh Singh 1 month ago
parent 0f0fe13576
commit 5e2817e3d0

@ -0,0 +1,173 @@
# Theme chrome audit — after the navbar redesign
Snapshot 2026-08-24 · baselined on [#5397](https://github.com/vuejs/vitepress/pull/5397) (`navbar-redesign`) · `main` @ v2.0.0-alpha.19
> Generated with Claude Code: five parallel sweeps (navbar core · local nav + outline · sidebar · theming/CSS/layout · a11y + i18n) over **open issues**, **issues closed as not planned**, **open PRs**, and **closed-unmerged PRs** touching what #5397 ships or its follow-ups plan to touch — plus a line-by-line review of [#1273](https://github.com/vuejs/vitepress/pull/1273). ≈120 unique records assessed, 60+ deep-dived. Every "solved" and "still broken" claim was verified against the `navbar-redesign` checkout, not taken from the threads. Spot-check before acting on close recommendations.
## Files
The per-area pages carry the full write-ups (what each record asks, why it was declined, what verdict it gets and why):
- [navbar.md](./navbar.md) — the bar, mobile screen, dropdowns, overflow
- [local-nav.md](./local-nav.md) — VPLocalNav, outline dropdown, return-to-top
- [sidebar.md](./sidebar.md) — drawer, groups, scroll, data layer
- [theming-css.md](./theming-css.md) — variables, layout shift, modern CSS
- [a11y-i18n.md](./a11y-i18n.md) — focus, ARIA, labels, direction
- [pr-1273.md](./pr-1273.md) — what [#1273](https://github.com/vuejs/vitepress/pull/1273) contained vs what was adopted
## Fix now — live defects verified on the branch
Small, self-contained, and each lands on code [#5397](https://github.com/vuejs/vitepress/pull/5397) already touches.
| # | Defect | Where | Records |
| --- | --- | --- | --- |
| 1 | Custom `{ svg }` social links get a literal `aria-label=""`, which wins the name computation over any SVG `<title>` — the exact fix both declines prescribed provably cannot work. One fallback removal repairs bar, drawer and `⋯` menu at once. | `VPSocialLink.vue` | reopens [#2081](https://github.com/vuejs/vitepress/issues/2081), [#1325](https://github.com/vuejs/vitepress/pull/1325), [#3014](https://github.com/vuejs/vitepress/pull/3014) |
| 2 | `outline: none` on `button, input, textarea, select` is only restored for `button:focus-visible` — text inputs have no focus indicator at all. WCAG 2.4.7 (AA), meeting the "where WCAG is clearly defined" carve-out. | `styles/base.css` | answers the carve-out in [#2085](https://github.com/vuejs/vitepress/issues/2085) |
| 3 | With `siteTitle: false`, the title link has no accessible name — `VPImage` defaults string logos to `alt=""`. Derive alt from the site title. | `VPNavBarTitle.vue` | [#5056](https://github.com/vuejs/vitepress/issues/5056) delta |
| 4 | The skip link targets `#VPContent`, which has no `tabindex="-1"`, and the aside precedes `<main>` in DOM order — sequential focus resumes at the outline. | `VPSkipLink.vue`, `VPDoc.vue` | adopts [#4940](https://github.com/vuejs/vitepress/pull/4940) |
| 5 | `VPFlyout`'s `.text` still carries `line-height: var(--vp-nav-height)` — the wrapped-label height bomb removed from menu links, left armed in the sibling component. Flex already centers it. | `VPFlyout.vue` | from [#1273](https://github.com/vuejs/vitepress/pull/1273) |
| 6 | No icon flex child in the bar has a shrink guard; fvsch measured the chevron compressed to 9px. The overflow engine prevents steady-state squeeze, not the transient before the ResizeObserver callback. | `VPFlyout.vue` et al. | from [#1273](https://github.com/vuejs/vitepress/pull/1273) |
## The local nav + sidebar rework — requirements register
The follow-up [#5397](https://github.com/vuejs/vitepress/pull/5397)'s body already names, now with concrete obligations. Largest bucket: 40+ records converge here. Full detail in [local-nav.md](./local-nav.md) and [sidebar.md](./sidebar.md).
### Geometry and presence
- **[#4359](https://github.com/vuejs/vitepress/issues/4359) / [#4393](https://github.com/vuejs/vitepress/pull/4393)** — the outline dropdown hard-codes `left: calc(var(--vp-sidebar-width) + 2rem)` unconditionally; the local nav does the same with `padding-left`. Give the local nav the presence-derived column offset the navbar just got (`--vp-nav-col-offset` → `0px` without a sidebar). Absorb the outcome of #4393 (LGTM'd, 16 comments, stalled) — not its per-component override — and credit its author.
- **[#3811](https://github.com/vuejs/vitepress/issues/3811)** — the nav-height probe is wrong twice: it appends to `document.body` where the `.hide-nav` override can't reach it (so `navbar: false` always resolves 4rem), and `vp-doc.css` bakes the local nav's height as a magic `2.9375rem` into every heading's `scroll-margin-top` ([#2334](https://github.com/vuejs/vitepress/issues/2334)). Replace both with a declared `--vp-local-nav-height` token; gate rendering on layout type, not raw scroll.
- **[#2071](https://github.com/vuejs/vitepress/issues/2071)** — `--vp-layout-top-height` threads through ten files and must be set by user-injected head JS (CLS + dismissal flash, visible on oxc.rs). Resolve the offset in SSR output. Also: `--vp-z-index-layout-top: 40` paints above the nav's 30.
- **[#1764](https://github.com/vuejs/vitepress/issues/1764)** — "only the secondary nav is sticky" leaves mobile home with nothing sticky at all. The obstacles the decline anticipated (curtain, `-100vw` bleed) are gone; state which element owns stickiness per layout.
### The outline dropdown, made first-class
- **[#5090](https://github.com/vuejs/vitepress/issues/5090) / [#5091](https://github.com/vuejs/vitepress/pull/5091)** — `useActiveAnchor` is single-consumer, which is why #5091 forked a parallel composable. Generalize it to two consumers; the fork and the dropdown's `data-allow-mismatch="style"` band-aid both disappear. The same generalization enables [#2146](https://github.com/vuejs/vitepress/issues/2146) (auto-expand the active outline branch, 👍8) in both surfaces.
- **[#3392](https://github.com/vuejs/vitepress/pull/3392)** — the near-full-height dropdown panel has Escape and scroll lock but no focus containment, no focus restore, no inert page behind it. Give it the flyouts' disclosure contract, and export the inert state for custom themes (the PR's explicit ask).
- **[#4953](https://github.com/vuejs/vitepress/issues/4953)** — the decline's load-bearing claim was that the dropdown suffices below 1280px. That makes the three items above obligations, not enhancements.
- **[#4521](https://github.com/vuejs/vitepress/issues/4521) / [#4522](https://github.com/vuejs/vitepress/pull/4522)** — return-to-top exists only as the dropdown's empty state. Hoist it into a shared control used by the dropdown and the aside footer, sharing `scrollToTop` and `returnToTopLabel`.
- **[#2297](https://github.com/vuejs/vitepress/pull/2297)** — `hasAside` (config-only) and `hasLocalNav` (headers-only) can disagree about whether a page has an outline. One derived predicate should feed the aside, the local nav, and the band handoff.
### Drawer, focus, and curtain
- **[#3392](https://github.com/vuejs/vitepress/pull/3392) + [#2329](https://github.com/vuejs/vitepress/pull/2329)** — the closed sidebar only gets `opacity: 0; transform: translateX(-100%)`, so every link stays tabbable; the open drawer never marks the page behind it inert; and the open-focus call is a no-op (`ref="navEl"` on the `<aside>`, `tabindex="-1"` on the inner `<nav>`). Caution from #2329: [#1332](https://github.com/vuejs/vitepress/issues/1332) is marked completed but reported regressed — re-verify.
- **[#4330](https://github.com/vuejs/vitepress/issues/4330)** — the swipe decline stands, but the unanswered half doesn't: `useSidebarControl`'s open/close isn't exported, so custom layouts click `.VPBackdrop` programmatically. Expose it.
- **[#3393](https://github.com/vuejs/vitepress/issues/3393) delta** — curtain restoration needs an opt-in flag (pure CSS cannot reproduce it — giladgd is version-pinned over this) and must be pointer-events-transparent (elringus: the old one intercepted clicks over visibly-unobscured links).
### Groups and structure
- **[#3806](https://github.com/vuejs/vitepress/pull/3806) vs [#4847](https://github.com/vuejs/vitepress/pull/4847)** — two competing native-`<details>` PRs, both CONFLICTING after `ed2bfb26`; pick a lineage before the `::details-content` work. Constraints from the threads: a linked group heading must still navigate (`<summary>` can't swallow the link), auto-uncollapse on navigating into a collapsed group must drive native `open`, the summary-link nesting must not recreate [#3517](https://github.com/vuejs/vitepress/issues/3517), and Space-key toggling belongs in the e2e disclosure tests ([#3804](https://github.com/vuejs/vitepress/issues/3804)).
- **[#4211](https://github.com/vuejs/vitepress/issues/4211) + [#3441](https://github.com/vuejs/vitepress/issues/3441)** — collapse state is private per item, so one group can never close another. Hoisting it to shared state is the prerequisite for the requested opt-in accordion mode.
- **[#4683](https://github.com/vuejs/vitepress/issues/4683) + [#563](https://github.com/vuejs/vitepress/issues/563)** — the 5-level depth cap exists only because `textTag` computes `h{depth+2}` and heading levels run out at `h6`; #563's decline ("no more nested structure in theme-next") was invalidated the moment nesting shipped. Decouple nesting from headings, derive indentation from depth — both records fall together.
- **[#3621](https://github.com/vuejs/vitepress/pull/3621)** — items key on `i.text` (collides) while `VPSidebar` remounts the whole tree on any deep change. That remount will destroy preserved scroll and collapse state the moment the rework adds them; stable per-item identity is a structural requirement.
### Active item and scroll
- **[#5194](https://github.com/vuejs/vitepress/pull/5194)** — adopt this PR's behavioral spec wholesale: reveal on mount, route change and resize, suppressed once the user scrolls the sidebar manually (the part naive versions get wrong). Absorbs [#4345](https://github.com/vuejs/vitepress/issues/4345), [#4296](https://github.com/vuejs/vitepress/issues/4296), [#3426](https://github.com/vuejs/vitepress/issues/3426), [#2881](https://github.com/vuejs/vitepress/issues/2881) and PRs [#3654](https://github.com/vuejs/vitepress/pull/3654) (👍6), [#3901](https://github.com/vuejs/vitepress/pull/3901). Constraints: scroll-only (never focus-moving — it would fight the route-change focus reset in `VPSkipLink`), and composed with group auto-collapse in one pass, since collapsing moves the scroll target.
- **[#2257](https://github.com/vuejs/vitepress/issues/2257)** — `activeMatch` for sidebar items (👍9, highest-demand actionable sidebar issue). Constraint from [#5395](https://github.com/vuejs/vitepress/pull/5395): ancestor marking rides `has-active` and must not widen `aria-current` beyond the exact match.
### Data layer
- **[#4841](https://github.com/vuejs/vitepress/issues/4841) / [#4842](https://github.com/vuejs/vitepress/pull/4842)** — multi-sidebar keys prefix-match without trailing-slash normalization (`/api-examples.md` matches the `/a` sidebar). The public type documents keys as directories; make matching agree. Verify against the docs site's own config.
- **[#4114](https://github.com/vuejs/vitepress/issues/4114)** — nested `base` should extend the parent's, with an explicit escape hatch (absolute link or `base: null`) that answers the thread's real objection.
### Features that should ride the rework, not follow it
- **[#5105](https://github.com/vuejs/vitepress/pull/5105)** (desktop sidebar collapse; issue [#3071](https://github.com/vuejs/vitepress/issues/3071), prior attempt [#4739](https://github.com/vuejs/vitepress/pull/4739)) — two-thirds of its diff is navbar files #5397 rewrote; it cannot rebase. Once the sidebar owns its width as state, this is a variable flip plus a persisted preference. Settle the collapsed-state visual up front — that was the actual blocker.
- **[#1037](https://github.com/vuejs/vitepress/issues/1037) / [#4532](https://github.com/vuejs/vitepress/pull/4532)** (footer with sidebar, 👍7 + 👍6) — the PR implements the maintainer-conceded shape; the one open review note (full-width divider) is the same full-bleed problem the navbar surface just solved.
- **[#3534](https://github.com/vuejs/vitepress/issues/3534)** (breadcrumbs, 👍7 · 11c) — derive the ancestor path from the resolved sidebar structure with frontmatter as override; that answers the data-model objection for sidebar-bearing pages (leaf-only caveat stands elsewhere). As a second `<nav>` landmark it inherits #5397's conventions: locale-aware label, `aria-current` on the leaf only.
- **[#4048](https://github.com/vuejs/vitepress/issues/4048)** — a per-item slot insertion point is cheap while the item component is being rewritten, impossible to retrofit cleanly after.
- **[#3194](https://github.com/vuejs/vitepress/issues/3194)** — the aside is the last chrome using viewport-relative geometry where container-relative is correct; the natural home for container queries (the inverse of the bar, for which they were rightly rejected).
### SSR determinism — mechanism on record
- **[#4897](https://github.com/vuejs/vitepress/issues/4897) delta** — decide the local nav's presence from build output: stamp a class on `<html>` when the SSR'd HTML has header anchors and outline isn't disabled. Hard constraint (sapphi-red): header-less pages like `/guide/mpa-mode` must end with no local nav — he could not remove it on hydration. MPA mode is accepted collateral.
## Navbar follow-up additions
Full detail in [navbar.md](./navbar.md).
### Contracts the #4000 follow-up must widen
- **[#2866](https://github.com/vuejs/vitepress/pull/2866) + [#2831](https://github.com/vuejs/vitepress/issues/2831)** — slot content is invisible to the overflow engine in **both axes**: width (re-introduces the crowding [#2842](https://github.com/vuejs/vitepress/issues/2842) fixed) and height (the fixed-height bar lets over-tall slot content paint over the page — the [#1273](https://github.com/vuejs/vitepress/pull/1273) delta). The [#4000](https://github.com/vuejs/vitepress/issues/4000) contract should cover slot content and decide once: measure-and-collapse, grow, or document fixed-height as a constraint.
- **[#3069](https://github.com/vuejs/vitepress/pull/3069)** — nested groups render but aren't disclosures: `VPMenuGroup` still emits a static `<p class="title">`. The nested-dropdowns follow-up ([#3816](https://github.com/vuejs/vitepress/issues/3816)) needs per-group `collapsed?: boolean` with the disclosure treatment the screen variant already has. Absorb the feature, not the patch — it reintroduces nested `role="button"`.
- **[#3407](https://github.com/vuejs/vitepress/issues/3407)** — separator nav items need zero collapse priority in `computeNavFit`, or one stranded at the visible/collapsed boundary renders as a dangling rule.
### One-point-of-change wins created by the unification
- **[#4364](https://github.com/vuejs/vitepress/issues/4364)** — nav links ignore `rewrites`; every nav href now flows through `useNavItemLink`, so normalization has exactly one insertion point.
- **[#4347](https://github.com/vuejs/vitepress/issues/4347)** — the locale switcher links to 404s; all three renderings share one `useLangs` call, so an existence check with ancestor fallback fixes every surface. Cross-link [#3312](https://github.com/vuejs/vitepress/pull/3312)/[#3275](https://github.com/vuejs/vitepress/issues/3275) (content-side of the same gap).
- **[#3383](https://github.com/vuejs/vitepress/pull/3383)** — kiaking's counter-proposal (transparent by default on `layout: page` with no sidebar and no local nav) is now one extra condition on the single state rule; the variable route he prescribed also genuinely works now.
- **[#2085](https://github.com/vuejs/vitepress/issues/2085)** — three hover treatments coexist (flyouts → brand, social links → text-1, title → none). Now that all are siblings in one component set, a shared nav-interactive token is a contained change.
### New surface areas worth planning
- **[#3086](https://github.com/vuejs/vitepress/issues/3086) + [#2913](https://github.com/vuejs/vitepress/pull/2913) + [#3123](https://github.com/vuejs/vitepress/pull/3123)** — the theme has zero `@media print` rules. With one surface and one geometry, a print block is small and self-contained; the per-mode sprawl that stalled #2913's review is gone.
- **[#522](https://github.com/vuejs/vitepress/issues/522)** — per-page navbar hiding: kiaking's last word was "I think we can add this feature… open a new issue" — nobody did. `--vp-nav-height` is now the single geometry input, so zeroing it per-page is one rule (and the `.hide-nav` override must live where all consumers can see it).
- **[#4141](https://github.com/vuejs/vitepress/issues/4141)** — inline SVG logo: the one bar element CSS can't theme. Supersedes not-planned [#1742](https://github.com/vuejs/vitepress/issues/1742).
- **[#2706](https://github.com/vuejs/vitepress/issues/2706)** — a slot *below* the bar for full-bleed banners; composes with the surface and `--vp-nav-height` consumers, natural home in `VPNav.vue`.
- **[#3773](https://github.com/vuejs/vitepress/issues/3773)** — declined because [768, 1280) had no nav screen and no overflow container; the `⋯` menu removed half that premise. Define the slot contract for the band instead of rendering nothing silently.
## Theming & CSS program
Full detail in [theming-css.md](./theming-css.md).
- **[#4125](https://github.com/vuejs/vitepress/pull/4125)** — extend `@layer` from `base.css` (already layered via [#4425](https://github.com/vuejs/vitepress/issues/4425)) to the chrome component styles; the decline's browser floor no longer exists. Kept class aliases mean precedence changes without selector changes. Pairs with [#3021](https://github.com/vuejs/vitepress/issues/3021) — any renaming must be additive alongside the aliases.
- **[#1147](https://github.com/vuejs/vitepress/issues/1147)** — make `--vp-layout-max-width` and `--vp-sidebar-width` load-bearing in `VPDoc`/`VPSidebar`/`VPContent`; the named cause (hardcoded `max-width: 688px`-style numbers) is the same duplication class #5397 deleted inside the navbar.
- **[#3433](https://github.com/vuejs/vitepress/issues/3433)** — `color-mix()` with `light-dark()` in one pass (brc-dd's own #4425 roadmap pairs them; the decline's browser floor is dead). Constraint from [#4471](https://github.com/vuejs/vitepress/issues/4471): `light-dark()` hard-codes the two-scheme model — keep a plain-token override seam. Fold in [#3313](https://github.com/vuejs/vitepress/issues/3313): replace `opacity`-dimmed text with mixed tokens (fixes Persian/Arabic glyph-overlap artifacts, removes stray stacking contexts).
- **[#2056](https://github.com/vuejs/vitepress/pull/2056) delta** — the logical-properties decline was benefit-based, not correctness-based; the RTL work now supplies the benefit argument, and the attempt's one concrete regression (the outline marker stopped tracking — JS reads physical offsets) is the required regression test. Completing it unlocks [#2794](https://github.com/vuejs/vitepress/issues/2794): the unconditional `dir` writer in `app/index.ts` can become an opt-out or write-on-change guard, enabling runtime direction toggles.
- **[#5209](https://github.com/vuejs/vitepress/issues/5209)** — line-height variables: the explicitly invited remainder after the rem migration.
## Three decisions to make once
Each has multiple competing records; deciding once retires the cluster.
1. **Scrollbar layout shift** — [#1054](https://github.com/vuejs/vitepress/issues/1054) (8c) · PRs [#1844](https://github.com/vuejs/vitepress/pull/1844) (👍5), [#5198](https://github.com/vuejs/vitepress/pull/5198) · [#4884](https://github.com/vuejs/vitepress/issues/4884) · delta on [#5310](https://github.com/vuejs/vitepress/issues/5310). Three open PRs, three mechanisms, one bug. Two facts the #5397 body's #5310 reasoning doesn't account for: kiaking explicitly reversed on the modal case ("Modal thing should be fixed"), and `scrollbar-gutter` was tested to fail exactly there (gutter paints above the modal scrim). #5397 shrinks #1844 to roughly one body rule by deleting the `100vw` bleed it compensated for.
2. **Appearance-transition recipe** — [#2347](https://github.com/vuejs/vitepress/pull/2347), 58 reactions (🚀33 ❤️19), the strongest signal in the audit. The hooks exist (`appearance.onChanged`, `disableTransition`); the "wonky with default theme" blocker was the six scattered nav background selectors, now one color-only surface. A documented recipe (gated on `prefers-reduced-motion`) satisfies the demand without shipping an opinionated animation.
3. **Route announcer** — [#1357](https://github.com/vuejs/vitepress/pull/1357), declined on a factually wrong premise ("we have this via nprogress" — a visual bar with no accessible output). Nothing announces SPA navigations; the `tabindex="-1"` sentinel #5397 added to `VPSkipLink` is the ready-made mount point for a polite live region.
## PR #1273 — adopted vs left behind
Full comparison in [pr-1273.md](./pr-1273.md).
**Adopted or superseded:** menu-link `min-height` + flex centering (credited in the #5397 body) · menu alignment and search spacing by equivalent means · the 768–960px band it declared out of scope is exactly where the overflow engine activates · the title border, its +1px accounting, and the border-color transition flash vanished with the border itself.
**Not implemented:** the flyout's line-height trick and the icon shrink guards (Fix now, items 5–6) — and the bar-growth mechanism (`min-height` below desktop, argued from WCAG 1.4.4) so over-tall content expands the bar instead of overflowing the page. Nowrap-plus-collapse resolves 1.4.4 for theme-managed content; custom slot content is the uncovered case, folded into the #4000 contract as the height axis.
## Housekeeping — closeable today
| Record | Why |
| --- | --- |
| [#3517](https://github.com/vuejs/vitepress/issues/3517) | Nested sidebar controls — fixed on `main` by `ed2bfb26` (native button toggles); the axe complaint is resolved. |
| [#5251](https://github.com/vuejs/vitepress/pull/5251) | Same fix landed the other way round in `ed2bfb26`; obsolete. |
| [#3351](https://github.com/vuejs/vitepress/issues/3351) | Aside marker visibility — resolved by merged [#5377](https://github.com/vuejs/vitepress/pull/5377), never closed. |
| [#2259](https://github.com/vuejs/vitepress/pull/2259) | Fully satisfied: switch titles shipped in [#3311](https://github.com/vuejs/vitepress/pull/3311), name/state split completed by #5397. |
| [#1332](https://github.com/vuejs/vitepress/issues/1332) | The opposite — marked completed but reported regressed ([#2329](https://github.com/vuejs/vitepress/pull/2329)); re-verify, #5397's inert work likely re-fixes it. |
| on #5397 merge | Keywords close [#2842](https://github.com/vuejs/vitepress/issues/2842), [#5364](https://github.com/vuejs/vitepress/pull/5364), [#5376](https://github.com/vuejs/vitepress/pull/5376); superseded unmerged PRs [#5097](https://github.com/vuejs/vitepress/pull/5097), [#1283](https://github.com/vuejs/vitepress/pull/1283), [#1448](https://github.com/vuejs/vitepress/pull/1448), [#2329](https://github.com/vuejs/vitepress/pull/2329), [#5074](https://github.com/vuejs/vitepress/pull/5074), [#4978](https://github.com/vuejs/vitepress/pull/4978), [#2260](https://github.com/vuejs/vitepress/pull/2260), [#2741](https://github.com/vuejs/vitepress/pull/2741), [#2455](https://github.com/vuejs/vitepress/pull/2455) can close with a pointer. |
## Confirmed not viable
Declines that still hold after the redesign — reasons re-checked, not assumed.
| Record | Standing reason |
| --- | --- |
| [#1297](https://github.com/vuejs/vitepress/issues/1297) (👍17) | Auto-sidebar from the filesystem — highest demand in the audit, but build-time config generation; no component rework can close it. |
| [#2912](https://github.com/vuejs/vitepress/pull/2912) (9c) | No-JS feature hiding — the separate-theme objection is structural; #5397 degrades cleanly (engine is all-visible during SSR) but deepens JS reliance. |
| [#4920](https://github.com/vuejs/vitepress/issues/4920) | CSS Modules — stable targetable class names are a contract #5397 strengthens. Layering ([#4125](https://github.com/vuejs/vitepress/pull/4125)) is the compatible alternative. |
| [#4413](https://github.com/vuejs/vitepress/issues/4413) · [#5221](https://github.com/vuejs/vitepress/issues/5221) · [#1008](https://github.com/vuejs/vitepress/issues/1008) · [#3160](https://github.com/vuejs/vitepress/issues/3160) | Product-design declines (tri-state switch, arrow-key paging, secondary navbars, full-width layout) untouched by the architecture. |
| [#4917](https://github.com/vuejs/vitepress/issues/4917) · [#4938](https://github.com/vuejs/vitepress/issues/4938) · [#2747](https://github.com/vuejs/vitepress/issues/2747) | Premise disputed with measurements · heading semantics correct with documented opt-out · working as designed. |
| Outline PR cluster | [#4735](https://github.com/vuejs/vitepress/pull/4735), [#3387](https://github.com/vuejs/vitepress/pull/3387), [#4634](https://github.com/vuejs/vitepress/pull/4634), [#4457](https://github.com/vuejs/vitepress/pull/4457), [#2189](https://github.com/vuejs/vitepress/pull/2189), [#2676](https://github.com/vuejs/vitepress/pull/2676) — all superseded by merged [#5377](https://github.com/vuejs/vitepress/pull/5377) or the rewritten `outline.ts`. |
## Demand signals
| Record | Signal | Where it lands |
| --- | ---: | --- |
| [#2347](https://github.com/vuejs/vitepress/pull/2347) | 58 reactions · 11c | Appearance-transition recipe (decision) |
| [#1297](https://github.com/vuejs/vitepress/issues/1297) | 👍17 · 19c | Not viable (build-time; plugins) |
| [#2257](https://github.com/vuejs/vitepress/issues/2257) | 👍9 · 7c | Rework — sidebar `activeMatch` |
| [#2146](https://github.com/vuejs/vitepress/issues/2146) | 👍8 | Rework — outline auto-expand |
| [#3534](https://github.com/vuejs/vitepress/issues/3534) | 👍7 · 11c | Rework — breadcrumbs from resolved sidebar |
| [#1037](https://github.com/vuejs/vitepress/issues/1037) + [#4532](https://github.com/vuejs/vitepress/pull/4532) | 👍7 + 👍6 | Rework — footer with sidebar |
| [#3654](https://github.com/vuejs/vitepress/pull/3654) cluster | 👍6 + 👍3 + 👍2 | Rework — auto-reveal active item ([#5194](https://github.com/vuejs/vitepress/pull/5194) spec) |
| [#1844](https://github.com/vuejs/vitepress/pull/1844) | 👍5 | Scrollbar-shift decision |
| [#3806](https://github.com/vuejs/vitepress/pull/3806) + [#4847](https://github.com/vuejs/vitepress/pull/4847) | 12c + 5c | Rework — native `<details>` groups |
| [#2913](https://github.com/vuejs/vitepress/pull/2913) | 7c | Print styles |

@ -0,0 +1,119 @@
# Accessibility & i18n of the theme chrome
Part of the [theme chrome audit](./README.md) · snapshot 2026-08-24 · baselined on [#5397](https://github.com/vuejs/vitepress/pull/5397) (`navbar-redesign`)
## Already solved by [#5397](https://github.com/vuejs/vitepress/issues/5397)
- **[#1448](https://github.com/vuejs/vitepress/issues/1448) — fix(theme): Make the menu traversable only when the menu is visible** (kind: pr-unmerged; demand: 👍0, 3c)
- Sets `tabindex="-1"` so the mobile menu is not tabbable while hidden; reviewer follow-up demanded focus trapping, an Escape listener, and focus restore before it could land, and the author never returned.
- [#5397](https://github.com/vuejs/vitepress/issues/5397) delivers the whole reviewer checklist: `Layout.vue` applies `:inert="isScreenOpen"` to skip link, local nav, sidebar, content and footer; `VPNavScreen` has `onKeyStroke('Escape')` + `screenTriggerEl.focus()`; screen accordions are real `v-show` disclosures so collapsed locale links leave the tab order.
- **[#2329](https://github.com/vuejs/vitepress/issues/2329) — feat(theme): use inert to avoid traverse menus and content with keyboard** (kind: pr-unmerged; demand: 👍0, 0c)
- Adds `inert` to content behind the open nav screen on narrow viewports (revival of [#1332](https://github.com/vuejs/vitepress/issues/1332)/[#1491](https://github.com/vuejs/vitepress/issues/1491)).
- Superseded: same mechanism now in `Layout.vue` lines 46–93, with `isScreenOpen` hoisted to module scope in `composables/nav.ts` exactly as this PR proposed.
- **[#2259](https://github.com/vuejs/vitepress/issues/2259) — feat(theme): add custom tooltip text for darkModeSwitchLabel** (kind: pr-unmerged; demand: 👍0, 0c)
- Asks for a translatable tooltip on the appearance switch instead of hardcoded English.
- Config landed earlier via [#3311](https://github.com/vuejs/vitepress/issues/3311) (`lightModeSwitchTitle`/`darkModeSwitchTitle`); [#5397](https://github.com/vuejs/vitepress/issues/5397) completes it by splitting the roles — `aria-label` = `darkModeSwitchLabel` (stable name), `title` = the mode-specific action hint. Safe to close.
- **[#3517](https://github.com/vuejs/vitepress/issues/3517) — accessibility: interactive controls should not be nested** (kind: issue-open; demand: 👍0, 1c)
- Sidebar group header carries `tabindex="0"`/`role="button"` while containing a heading and a nested caret button; the 2026-08-07 comment reports 102 axe-core failing nodes on a 326-page site.
- Fixed, but by `ed2bfb26` ("render sidebar group toggles as native buttons"), already on `main` and confirmed by [#5366](https://github.com/vuejs/vitepress/issues/5366) closing as COMPLETED — not by [#5397](https://github.com/vuejs/vitepress/issues/5397). Verified: `VPSidebarItem.vue` `.item` now has no `role`/`tabindex` and the caret is a real `<button type="button">`. Issue is stale and should be closed.
- **[#5251](https://github.com/vuejs/vitepress/issues/5251) — fix: avoid nested sidebar controls** (kind: pr-open; demand: 👍0, 0c)
- Obsolete against `main`: `ed2bfb26` already solved the nesting the other way round. Should be closed.
- **[#5371](https://github.com/vuejs/vitepress/issues/5371) — fix(theme): remove invalid role and tabindex from VPSidebarItem wrapper** (kind: pr-unmerged; demand: 👍0, 0c)
- Exactly what `ed2bfb26` shipped; already closed, no action.
## Fits a planned follow-up
- **[#3804](https://github.com/vuejs/vitepress/issues/3804) — sidebar: use native <details> for collapsible groups** (kind: issue-open; demand: 👍0, 1c)
- Sidebar groups toggle on click and Enter but not Space, diverging from native disclosure behaviour.
- Fits the "`details`/`summary` with `::details-content`" follow-up. Requirement the PR body does not yet state: the Space-key half is already fixed on `main` by `ed2bfb26`'s native button, so the residual ask is purely the native element plus animatable `::details-content` — the follow-up should scope to that, not to keyboard parity.
- **[#3806](https://github.com/vuejs/vitepress/issues/3806) — sidebar uses native <details> for collapsible groups [#3804, also [#3517](https://github.com/vuejs/vitepress/issues/3517)]** (kind: pr-open; demand: 👍0, 12c)
- Full implementation of [#3804](https://github.com/vuejs/vitepress/issues/3804); author has bumped it for two years and offered to rebase.
- Same follow-up. Requirement it adds: it also deletes the custom JS toggle and the `collapsed` ref, so the follow-up must reconcile with `useSidebarItemControl`'s auto-expand-on-active-route behaviour (`nextTick(() => collapsed.value = false)`) which `<details>` cannot express without an `open` binding.
- **[#4847](https://github.com/vuejs/vitepress/issues/4847) — Accordions now use native details/summary** (kind: pr-open; demand: 👍0, 5c)
- Second, independent `<details>` implementation; both authors acknowledged the overlap.
- Same follow-up; pick one branch. Requirement it adds: it keeps the existing caret icon and rotation as pure CSS on `summary`, the cheaper path for preserving current visuals.
- **[#3392](https://github.com/vuejs/vitepress/issues/3392) — feat(config, theme): add 'inert' attribute to prevent unnecessary traversal of hidden content** (kind: pr-open; demand: 👍0, 1c)
- Global `inert` controls with the state exported from the default theme, plus a focus trap in the "On this page" outline popup. Author's own comment: "Sidebar not woking...".
- The nav-screen half is done by [#5397](https://github.com/vuejs/vitepress/issues/5397); the rest fits the sidebar/local-nav follow-up. Two requirements the PR body does not account for: (a) `inert` is never applied when the *sidebar drawer* is open, so content behind the mobile sidebar stays tabbable — verified, `Layout.vue` only keys off `isScreenOpen`; (b) it asks for the inert state to be *exported* so custom layouts can participate.
- **[#4296](https://github.com/vuejs/vitepress/issues/4296) — Opening or navigating to sidebar links should focus/scroll to the sidebar item** (kind: issue-open; demand: 👍2, 3c)
- Active sidebar item stays off-screen after navigation.
- Fits the sidebar rework follow-up. Requirement it adds: the reveal must be scroll-only, not focus-moving, or it will fight the route-change focus reset [#5397](https://github.com/vuejs/vitepress/issues/5397) introduced in `VPSkipLink.vue` (`watch(() => route.path, () => backToTop.value?.focus())`).
- **[#3901](https://github.com/vuejs/vitepress/issues/3901) — feat: Improve Sidebar and Aside Link Visibility on Mount and Route Change** (kind: pr-open; demand: 👍0, 0c)
- Same follow-up; the ready-made patch for [#4296](https://github.com/vuejs/vitepress/issues/4296).
- **[#2257](https://github.com/vuejs/vitepress/issues/2257) — Highlight active sidebar item when child page is loaded** (kind: issue-open; demand: 👍9, 7c)
- Highest-demand item in this sweep: visiting `/guide/subpage` leaves `/guide`'s sidebar entry unhighlighted.
- Fits the sidebar rework follow-up. Requirement it adds: [#5395](https://github.com/vuejs/vitepress/issues/5395) deliberately separated `isCurrentLink` (exact match, drives `aria-current="page"`) from `isActiveLink`; ancestor marking must ride the `has-active` class and must **not** widen `aria-current`, or multiple links will claim to be the current page.
## New candidates for future rework
- **[#5056](https://github.com/vuejs/vitepress/issues/5056) — Improve screen reader accessibility** (kind: issue-open; demand: 👍0, 0c)
- Umbrella list of six screen-reader defects; remaining unchecked items after [#5397](https://github.com/vuejs/vitepress/issues/5397), as deltas:
- **Done, but not by [#5397](https://github.com/vuejs/vitepress/issues/5397)**: `aria-current="page"` on nav links came from [#5395](https://github.com/vuejs/vitepress/issues/5395); nav items and social links became `<ul>/<li>` in [#5326](https://github.com/vuejs/vitepress/issues/5326) — [#5397](https://github.com/vuejs/vitepress/issues/5397)'s contribution is extending the list markup to the mobile screen and `⋯` menu and replacing the visually-hidden span with locale-aware `navMenuLabel`.
- **(delta) logo alt text is still unfixed**: `VPNavBarTitle.vue` renders `<VPImage v-if="theme.logo" class="logo" :image="theme.logo" />` with no `alt`, and `VPImage` defaults to `alt=""` for string logos — with `siteTitle: false` the title link then has *no accessible name at all*, since the sibling `<span>` is not rendered.
- **(delta) home hero actions are still not a nav landmark**.
- **(delta) local search is broadly instrumented** (combobox/listbox/`aria-activedescendant`/`aria-live`) but `VPLocalSearchBox.vue:512` still hardcodes English `aria-label="Loading search results"` with no locale option.
- **[#2081](https://github.com/vuejs/vitepress/issues/2081) — socialLinks items are not accessible** (kind: issue-not-planned; demand: 👍0, 1c)
- Custom-SVG social links expose `role="link"` with no accessible name. Declined 2023 with "Move title inside your svg as an element".
- **Decline reason is now invalidated by the code, and [#5397](https://github.com/vuejs/vitepress/issues/5397) widens the blast radius.** `VPSocialLink.vue:55` renders `:aria-label="ariaLabel ?? (typeof icon === 'string' ? icon : '')"` — for an object `{svg}` icon without `ariaLabel` this emits a literal `aria-label=""`, which wins the accessible-name computation over any `<title>` inside the inline SVG, so the recommended workaround provably cannot work. [#5397](https://github.com/vuejs/vitepress/issues/5397) renders social links in three places from one `VPNavSocialLinks`, so a single fix — drop the empty-string fallback so the SVG `<title>` can surface — repairs all three at once.
- **[#1325](https://github.com/vuejs/vitepress/issues/1325) — fix(socialIcons): Add aria-label attribute** (kind: pr-unmerged; demand: 👍0, 1c)
- Declined 2022: "we already have `title` present in the SVGs, so assistive technologies should be fine with that".
- Same invalidation as [#2081](https://github.com/vuejs/vitepress/issues/2081): the theme later adopted an `aria-label` defaulting to `''`, which actively suppresses the `title` the decline relied on. The new unified `VPNavSocialLinks` is the natural place to land the fix.
- **[#4940](https://github.com/vuejs/vitepress/issues/4940) — fix(theme): skip link jumps to aside instead main content heading/anchor** (kind: pr-open; demand: 👍0, 2c)
- "Skip to content" lands on the aside rather than the main heading; also pins the skip link with `position: fixed`. Blocked on the author's own "don't merge yet", then bluwy asked for status on 2026-08-20 with no reply.
- Confirmed live on the branch: `VPSkipLink.vue:21` targets `href="#VPContent"`, but `#VPContent` has **no `tabindex="-1"`** (only two `tabindex` attributes exist in the whole theme), and inside it `VPDocAside` is rendered at `VPDoc.vue:30` *before* `<main class="main">` at line 45 — so the sequential focus start point sits ahead of the outline links. The fix belongs with the local-nav/sidebar follow-up: give the skip target a real `tabindex="-1"` on `<main>`.
- **[#2747](https://github.com/vuejs/vitepress/issues/2747) — langMenuLabel does nothing** (kind: issue-not-planned; demand: 👍0, 1c)
- Decline ("that option is the aria-label") is technically correct and still holds.
- **Anchor for the real gap it exposes**: four chrome strings still have *no locale option whatsoever*: `VPSidebar.vue:56` `"Sidebar Navigation"`, `VPSidebarItem.vue` `aria-label="toggle section"` (also non-descriptive — never names the section), `VPDocFooter.vue:50` `"Pager"` (from [#3801](https://github.com/vuejs/vitepress/issues/3801) for [#3516](https://github.com/vuejs/vitepress/issues/3516)), and `VPLocalSearchBox.vue:512` `"Loading search results"`. The sidebar/local-nav follow-up should extend the label family [#5397](https://github.com/vuejs/vitepress/issues/5397) established.
- **[#2085](https://github.com/vuejs/vitepress/issues/2085) — navbar inconsistent behaviour** (kind: issue-open; demand: 👍0, 6c)
- Inconsistent hover/focus contrast reports; yyx990803 pushed back as "mostly your personal preferences" while carving out "we will try our best to adhere to WCAG standards where things are clearly defined".
- **The carve-out is met by a defect found in this audit**: `styles/base.css:277` sets `button:focus, input:focus, textarea:focus, select:focus { outline: none }` but only restores it for `button:focus-visible` at line 284. `input`, `textarea` and `select` get **no focus indicator at all** — no `input:focus-visible` rule anywhere in `src/client`, and `.search-input` in `VPLocalSearchBox.vue:706` defines none of its own. A clearly-defined WCAG 2.4.7 (AA) failure on the theme's primary text input, not a preference. Fits naturally into [#5397](https://github.com/vuejs/vitepress/issues/5397)'s focus-visible pass.
- **[#2794](https://github.com/vuejs/vitepress/issues/2794) — Disable automatically set direction in <html>** (kind: issue-open; demand: 👍0, 3c)
- `src/client/app/index.ts:48` unconditionally rewrites `document.documentElement.dir` on every route change, so a runtime direction toggle flickers ltr→rtl on each navigation. bluwy re-confirmed the cause on 2026-07-08.
- Fits alongside the logical-properties follow-up but needs its own decision: an opt-out (`dir: false`) or a "only write when it differs" guard. [#5397](https://github.com/vuejs/vitepress/issues/5397) already improved the neighbouring surface — `VPNavTranslations.vue` now emits per-locale `lang`/`hreflang`/`dir` — so the app-level `dir` writer is the last unconditioned direction mutation left.
- **[#4347](https://github.com/vuejs/vitepress/issues/4347) — Language selector (i18n) points to missing URLs** (kind: issue-open; demand: 👍0, 0c)
- The locale switcher maps `/en/foo/bar` to `/es/foo/bar` even when that page does not exist → 404; asks fallback to nearest existing ancestor.
- Lands directly on code [#5397](https://github.com/vuejs/vitepress/issues/5397) rewrote: `useLangs({ linkToCorrespondingPage: true })` feeding the unified `VPNavTranslations` (bar flyout, screen accordion and `⋯` group all share one `localeLinks`), so a single resolution change fixes all three renderings. Needs build-time knowledge of which locale pages exist. Cross-link [#3312](https://github.com/vuejs/vitepress/issues/3312)/[#3275](https://github.com/vuejs/vitepress/issues/3275) (content-side fallback of the same gap).
- **[#3241](https://github.com/vuejs/vitepress/issues/3241) — Change Prev / Next Text globally** (kind: issue-open; demand: 👍2, 0c) + **[#2535](https://github.com/vuejs/vitepress/issues/2535) — Cannot change prev/next labels when disabling them globally** (kind: issue-open; demand: 👍2, 1c)
- Same defect in doc-footer chrome i18n: `VPDocFooter.vue:62,77` render `theme.docFooter?.prev || 'Previous page'`, conflating label and visibility boolean (since [#2317](https://github.com/vuejs/vitepress/issues/2317)). Fix together by separating label from flag, matching the label-only options pattern [#5397](https://github.com/vuejs/vitepress/issues/5397) established.
- **[#1357](https://github.com/vuejs/vitepress/issues/1357) — Route Announcer for Vitepress 📢** (kind: pr-unmerged; demand: 👍0, 2c)
- Adds an `aria-live` route announcer modelled on Next.js. Closed 2022 with "Seems like we have this via ngprogress".
- **Decline reason is factually wrong** — nprogress is a visual progress bar with no accessible output — and nothing has replaced it: the only `aria-live` in the entire default theme is the local search spinner. [#5397](https://github.com/vuejs/vitepress/issues/5397) added *focus* handling on route change (`VPSkipLink.vue` focuses a `tabindex="-1"` sentinel) but no *announcement*; the sentinel span is the ready-made mount point for a polite live region.
- **[#3534](https://github.com/vuejs/vitepress/issues/3534) — breadcrumb** (kind: issue-open; demand: 👍7, 11c)
- A breadcrumb is a second `<nav>` landmark in doc chrome, so it inherits [#5397](https://github.com/vuejs/vitepress/issues/5397)'s conventions directly: its own locale-aware label in the `navMenuLabel` family, `aria-current="page"` on the leaf per [#5395](https://github.com/vuejs/vitepress/issues/5395)'s exact-match rule, and no duplicate of the sidebar's labelled landmark.
## Not viable
- **[#4938](https://github.com/vuejs/vitepress/issues/4938) — Headlines in the navigation are breaking the headline structure** (kind: issue-not-planned; demand: 👍0, 5c) — Decline stands: `h{depth+2}` is correct sectioning semantics; documented transform-plugin opt-out exists.
- **[#5221](https://github.com/vuejs/vitepress/issues/5221) — Navigate to next / previous page with arrow keys** (kind: issue-not-planned; demand: 👍0, 5c) — Declined 2026-08-22 on demand/discoverability grounds; navbar rework doesn't touch the reasoning.
- **[#5278](https://github.com/vuejs/vitepress/issues/5278) — Feature/arrow key nav** (kind: pr-unmerged; demand: 👍0, 0c) — Falls with [#5221](https://github.com/vuejs/vitepress/issues/5221).
- **[#466](https://github.com/vuejs/vitepress/issues/466) — Make Vitepress Accessible** (kind: issue-not-planned; demand: 👍0, 2c) — Closed as unworkably broad; [#5056](https://github.com/vuejs/vitepress/issues/5056) is the successor.
- **[#1794](https://github.com/vuejs/vitepress/issues/1794) — Search button shown when non-functional (without JavaScript)** (kind: issue-not-planned; demand: 👍0, 2c) — No no-JS story exists; [#5397](https://github.com/vuejs/vitepress/issues/5397) deepens the JS dependency (ResizeObserver overflow engine).
- **[#4330](https://github.com/vuejs/vitepress/issues/4330) — Open sidebar with touch navigation (swipe)** (kind: issue-not-planned; demand: 👍0, 3c) — Edge-swipe conflict decline (2026-07-09) unaffected.
- **[#1089](https://github.com/vuejs/vitepress/issues/1089) — Navbar for i18n** + **[#1267](https://github.com/vuejs/vitepress/issues/1267) — [i18n] NavMulti config** (issue-not-planned) — Delivered via per-locale `themeConfig` overrides ([#631](https://github.com/vuejs/vitepress/issues/631)/[#1339](https://github.com/vuejs/vitepress/issues/1339)).
- **[#1631](https://github.com/vuejs/vitepress/issues/1631) — feat: add Set custom menu/return to top labels** (kind: pr-unmerged) — Shipped as `sidebarMenuLabel`/`returnToTopLabel`.
- **[#2980](https://github.com/vuejs/vitepress/issues/2980) — a11y: Headings read verbosely by VoiceOver** + **[#2982](https://github.com/vuejs/vitepress/issues/2982)** (pr-unmerged) — Markdown-it heading rendering, not chrome; overlaps open [#4609](https://github.com/vuejs/vitepress/issues/4609).
- **[#4390](https://github.com/vuejs/vitepress/issues/4390) — fix(theme/a11y): role="main"** (kind: pr-unmerged) — Withdrawn; theme already renders a real `<main>`.
- **[#5372](https://github.com/vuejs/vitepress/issues/5372) — :lang(A, B) Chromium flaw** (kind: pr-unmerged) — Vite down-compiles in production; dev-only, closed by agreement.
- **[#5209](https://github.com/vuejs/vitepress/issues/5209) — New CSS custom properties** + **[#703](https://github.com/vuejs/vitepress/issues/703) — rem font sizing** — Delivered by [#5323](https://github.com/vuejs/vitepress/issues/5323) ("feat: convert px to rem", merged 2026-08-09).
- **[#3312](https://github.com/vuejs/vitepress/issues/3312) / [#3275](https://github.com/vuejs/vitepress/issues/3275) — i18n fallback for untranslated pages** (pr-open 👍5 / issue-open 👍2) — Content routing/build output, not theme chrome; cross-link with [#4347](https://github.com/vuejs/vitepress/issues/4347) (chrome-side symptom of the same gap).

@ -0,0 +1,111 @@
# Local nav & outline chrome
Part of the [theme chrome audit](./README.md) · snapshot 2026-08-24 · baselined on [#5397](https://github.com/vuejs/vitepress/pull/5397) (`navbar-redesign`)
## Already solved by [#5397](https://github.com/vuejs/vitepress/issues/5397)
- **[#2329](https://github.com/vuejs/vitepress/issues/2329) — feat(theme): use inert to avoid traverse menus and content with keyboard** (kind: pr-unmerged; demand: 👍0, 0c)
- Moves `isScreenOpen` out of the composable closure and applies `inert` in `Layout.vue` so the covered page (including the local nav) is unreachable by keyboard while the mobile screen is open.
- [#5397](https://github.com/vuejs/vitepress/issues/5397) lands exactly this, same files: `nav.ts` hoists `isScreenOpen`/`screenTriggerEl` to module scope, and `Layout.vue` passes `:inert="isScreenOpen"` to `VPLocalNav`, `VPSidebar`, `VPContent`, `VPFooter` and `VPSkipLink` (which gained an explicit `inert` prop because it has two root nodes).
## Fits a planned follow-up
- **[#4359](https://github.com/vuejs/vitepress/issues/4359) — Local navigation dropdown misplaced without sidebar** (kind: issue-open; demand: 👍0, 5c)
- The outline dropdown panel is offset by the sidebar width even on pages with no sidebar, so it floats off-position. Maintainer's stated fix in-thread: `--vp-sidebar-width` should be 0 when there is no sidebar.
- Local-nav/sidebar rework. Still unfixed on `navbar-redesign`: `VPLocalNavOutlineDropdown.vue:172` keeps `left: calc(var(--vp-sidebar-width) + 2rem)` unconditionally at `@media (min-width: 60rem)`, and `styles/vars.css:541` defines `--vp-sidebar-width: 17rem` as a static token that is never zeroed. The rework must make the sidebar column width a state-driven variable (0 when `hasSidebar` is false) so the dropdown, and every other consumer, positions from one source of truth instead of per-component overrides.
- **[#4393](https://github.com/vuejs/vitepress/issues/4393) — fix(components): Local navigation location error** (kind: pr-open; demand: 👍0, 16c)
- Community fix for [#4359](https://github.com/vuejs/vitepress/issues/4359): adds a `has-sidebar` class to `VPLocalNavOutlineDropdown` and a `:not(.has-sidebar) .items { left: 32px }` override. Approved by a contributor, stalled awaiting a maintainer for months.
- Local-nav/sidebar rework. The rework must absorb the outcome but not the mechanism — this PR adds a second hard-coded `left` and a duplicate `useSidebar()` call inside the dropdown, which is the exact per-component-override pattern [#5397](https://github.com/vuejs/vitepress/issues/5397) removed from the navbar. Implement at the variable level (zeroed `--vp-sidebar-width`) and credit/close this PR.
- **[#5090](https://github.com/vuejs/vitepress/issues/5090) — Theme suggestion: mobile TOC active highlight** (kind: issue-open; demand: 👍0, 0c)
- The local nav's "On this page" dropdown renders the outline with no active-heading highlight, unlike the desktop aside.
- Local-nav/sidebar rework. `useActiveAnchor` is currently single-consumer: it is called only from `VPDocAsideOutline.vue:16` and binds one container plus one marker element. The rework must make active-anchor tracking support two simultaneous consumers (aside outline and local-nav dropdown), so the dropdown gets `.active` and a marker without a parallel implementation.
- **[#5091](https://github.com/vuejs/vitepress/issues/5091) — feat: mobile TOC active highlight** (kind: pr-open; demand: 👍0, 0c)
- Implements [#5090](https://github.com/vuejs/vitepress/issues/5090) by adding a second composable, `useFloatActiveAnchor(items, marker, open)`, plus a duplicated `.outline-marker` and `.outline-link.active` ruleset inside the dropdown.
- Local-nav/sidebar rework. This PR is the evidence for the requirement above: the fork exists only because `useActiveAnchor` cannot serve a second, conditionally-mounted container. Rework should generalize the one composable and delete the need for `useFloatActiveAnchor`.
- **[#3392](https://github.com/vuejs/vitepress/issues/3392) — feat(theme): add 'inert' attribute to prevent unnecessary traversal of hidden content** (kind: pr-open; demand: 👍0, 1c)
- Successor to [#2932](https://github.com/vuejs/vitepress/issues/2932): global inert controls exported from the default theme, plus a focus trap in the "On this page" popup. Touches `VPLocalNavOutlineDropdown.vue`, `Layout.vue`, `nav.ts`, `sidebar.ts`, `VPSkipLink.vue`.
- Local-nav/sidebar rework. [#5397](https://github.com/vuejs/vitepress/issues/5397) took only the screen/Layout inert half. The unabsorbed delta is the local-nav dropdown itself: on the branch it has Escape and `useBodyScrollLock` but **no focus trap, no focus return to the trigger on close, and no inert on the page behind it** — even though it renders a near-full-height panel. The rework must give the dropdown the same disclosure contract [#5397](https://github.com/vuejs/vitepress/issues/5397) gave the navbar flyouts (focus containment while open, restore focus to the trigger on Escape/dismiss), and should also export the inert state so custom themes can reuse it.
- **[#3811](https://github.com/vuejs/vitepress/issues/3811) — 'Return to top' button is always visible in custom layout page** (kind: issue-open; demand: 👍0, 0c)
- With `layout: foo` + `navbar: false` + `sidebar: false`, the local nav renders as a bare "Return to top" bar permanently, even at scroll top.
- Local-nav/sidebar rework, specifically the nav-height DOM probe. Two concrete defects survive on the branch. (1) The probe in `VPLocalNav.vue:24-32` does `document.body.appendChild(probe)` with `height: var(--vp-nav-height)`, but the `--vp-nav-height: 0px` override lives on `.hide-nav` (`vars.css:520-522`), which is on the layout div, not `body` — so the probe always resolves the `:root` value of `4rem` and the `isScrolled` gate is wrong whenever `navbar: false`. (2) The render gate `!isHome && (hasLocalNav || hasSidebar || isScrolled)` has no notion of custom layouts, so any non-`home` layout gets the `empty`+`fixed` local nav. The rework must replace the probe with a declared `--vp-local-nav-height` variable resolved in the right cascade scope, and gate rendering on layout type rather than raw scroll position.
- **[#2320](https://github.com/vuejs/vitepress/issues/2320) — fix(theme): 'Return to top' button is always visible in the home page** (kind: pr-unmerged; demand: 👍0, 2c)
- Earlier attempt at the same defect class (home-page variant, for [#2312](https://github.com/vuejs/vitepress/issues/2312)), patching `VPLocalNav.vue` and `sidebar.ts` together.
- Local-nav/sidebar rework. Its value is the precedent that the visibility decision cannot live in the raw `y >= navHeight` comparison and must be co-derived with sidebar/layout state — the same coupling the rework is unifying. The home case was fixed since; the custom-layout case ([#3811](https://github.com/vuejs/vitepress/issues/3811)) was not.
- **[#2071](https://github.com/vuejs/vitepress/issues/2071) — Add support of Global Notification** (kind: issue-open; demand: 👍0, 5c)
- Asks for a built-in dismissible banner. Reopened by a maintainer "for docs", then argued by a contributor (xsjcTony) that the slot approach is structurally broken.
- Local-nav/sidebar rework — this is the `--vp-layout-top-height` requirement. The thread documents exactly what the rework must own: with the `layout-top` slot, users must hand-maintain a media-query-matched banner height, inject a `<head>` script to avoid a dismissal flash on reload, and still get CLS because the fixed navbar and `VPLocalNav` (`padding-top: var(--vp-layout-top-height, 0px)`, `VPLocalNav.vue:75`) are offset by a JS-set variable. Requirement: the layout-top offset must be resolved in SSR output, not assigned by script after hydration, and the local nav's sticky origin must follow it without a magic constant. Cited real-world breakage: oxc.rs shows the banner flash.
- **[#2334](https://github.com/vuejs/vitepress/issues/2334) — Using slot doc-top messes up active heading determination in aside** (kind: issue-open; demand: 👍0, 0c)
- Content injected into `doc-top` shifts headings, so the active-heading calculation picks the wrong one. Reporter asks that it not depend on hard-coded constants.
- Local-nav/sidebar rework. Partly addressed already — the old `__PAGE_OFFSET__` is gone and `outline.ts:165-166` now reads per-header `scrollMarginTop`. What remains is a local-nav-owned magic number: `styles/components/vp-doc.css:7,13` bakes `2.9375rem` (the local nav's height) into every heading's `scroll-margin-top` below `80rem`, and assumes the local nav is present there regardless of whether it actually renders. There is no `--vp-local-nav-height` variable anywhere in the theme. The rework must introduce one and have both `vp-doc.css` and the runtime probe consume it, so scroll-margin tracks the chrome that is actually on screen.
- **[#4940](https://github.com/vuejs/vitepress/issues/4940) — fix(theme): skip link jumps to aside instead main content heading/anchor** (kind: pr-open; demand: 👍0, 2c)
- "Skip to content" resolves into the aside/outline rather than the main content heading; PR retargets to the first `h1` inside `#VPContent main` and makes the skip anchor `position: fixed`.
- Local-nav/sidebar rework. [#5397](https://github.com/vuejs/vitepress/issues/5397) touched `VPSkipLink.vue` but only to add the `inert` prop — `href="#VPContent"` is unchanged, so the bug ships. The rework must define the skip target relative to main content, and decide the skip link's relationship to the sticky local nav (a `position: fixed` skip link and a sticky sub-bar compete for the same top-of-page region).
- **[#3773](https://github.com/vuejs/vitepress/issues/3773) — '#nav-screen-content-after' does not work when screen width between [768,1280)** (kind: issue-not-planned; demand: 👍0, 8c)
- Neither `nav-screen-content-after` nor `nav-bar-content-after` gives the user a place to put extra nav content in the tablet band; reporter wants it folded into a popup menu there.
- Local-nav/sidebar rework. The decline reason was structural, not a rejection of the need: in [768,1280) there is no nav screen to append to and the bar had no overflow container. [#5397](https://github.com/vuejs/vitepress/issues/5397) removed half that premise by giving the bar a real `⋯` overflow menu at any width. The rework must define where page-level extra nav content lives in the band where the local nav is the primary chrome, and make the slot contract explicit rather than silently rendering nothing.
- **[#4953](https://github.com/vuejs/vitepress/issues/4953) — Show "On this page" sidebar more often in <1280px width** (kind: issue-not-planned; demand: 👍0, 2c)
- Asks for the aside outline below 1280px, showing that trimming `VPSidebar` padding 32→24px and dropping `VPDoc .content` horizontal padding frees the needed 80px at 1200px.
- Local-nav/sidebar rework. The decline rested on two claims, and the rework invalidates the load-bearing one: "no community interest" plus "even in smaller viewports, the *on this page* section is still expandable" — i.e. the local nav dropdown is the accepted substitute for the aside in this band. That makes it the rework's obligation to make the dropdown an adequate substitute (active highlight per [#5090](https://github.com/vuejs/vitepress/issues/5090), correct positioning per [#4359](https://github.com/vuejs/vitepress/issues/4359), focus handling per [#3392](https://github.com/vuejs/vitepress/issues/3392)). If the rework also revisits the 960/1280 band for [#4897](https://github.com/vuejs/vitepress/issues/4897), the padding budget in this issue is concrete input.
- **[#1764](https://github.com/vuejs/vitepress/issues/1764) — Navbar is not sticky in mobile breakpoint** (kind: issue-not-planned; demand: 👍1, 2c)
- The primary navbar is not sticky at mobile widths; only the secondary (local) nav is. Reporter notes the home layout has no secondary nav, so nothing is sticky there.
- Local-nav/sidebar rework. Declined as "a design choice — only the secondary nav is sticky", which is precisely the contract the rework is redefining. The unmet case is concrete and survives: on `isHome` and on any page where `VPLocalNav` does not render, the "secondary nav is the sticky one" rule leaves the user with no sticky chrome at all. The rework must state which element owns stickiness per layout, rather than leaving it implicit in the local nav's render gate.
- **(delta on known [#4897](https://github.com/vuejs/vitepress/issues/4897))** (kind: issue-open; demand: 👍0, 5c)
- Thread carries a concrete SSR mechanism the PR body does not record.
- The PR body says only "SSR determinism for the local nav ([#4897](https://github.com/vuejs/vitepress/issues/4897))". The thread adds: brc-dd proposes stamping a class on `<html>` **at build time**, conditional on the SSR'd HTML actually containing header anchors *and* outline/aside not being disabled in frontmatter/themeConfig — i.e. decide the local nav's presence from build output rather than on hydration. sapphi-red adds a hard constraint the rework must not break: header-less pages must end up with no local nav, and he was unable to remove it on hydration for `/guide/mpa-mode` (a real page with no headers). brc-dd also signals MPA mode is acceptable collateral. Record both the mechanism and the header-less-page test case.
- **(delta on known [#3393](https://github.com/vuejs/vitepress/issues/3393))** (kind: issue-not-planned; demand: 👍0, 5c)
- Thread contains a requirement and a constraint the PR body's "the sidebar curtain" bullet does not capture.
- Requirement: giladgd needs the curtain back as an **opt-in flag**, not a CSS recipe, and demonstrates why — it needs an extra DOM element plus the logic built around it, so a pure-CSS override cannot reproduce it (he is pinned to an old VitePress version over this). Constraint from elringus, which the rework's implementation must satisfy: the old curtain intercepted pointer events over visibly-unobscured links, so a restored curtain must be pointer-events-transparent. bluwy's "can't please everyone with different designs" is the standing counterweight, which an opt-in flag resolves rather than fights.
## New candidates for future rework
- **[#3534](https://github.com/vuejs/vitepress/issues/3534) — breadcrumb** (kind: issue-open; demand: 👍7, 11c)
- Requests a built-in breadcrumb trail above the page title. Highest 👍 count in this area and unaddressed anywhere in [#5397](https://github.com/vuejs/vitepress/issues/5397)'s plan.
- A rework of page-level nav chrome would incorporate it as the natural third element of the local nav / doc-top region, alongside the "Menu" trigger and the outline dropdown. The blocker recorded in-thread is data, not layout: brc-dd notes only the leaf can be inferred from the title, and peterbe's case (markdown copied in from another repo, no controllable frontmatter) rules out per-file frontmatter. So the requirement is deriving the ancestor path from the resolved sidebar structure, with frontmatter as an override — the same sidebar resolution the rework already has in hand.
- **[#4521](https://github.com/vuejs/vitepress/issues/4521) — Scroll to top button on desktop** (kind: issue-open; demand: 👍4, 3c)
- Wants a go-to-top affordance on long pages at desktop widths. Commenters note the mobile "Go to top" is not visible to them either.
- A rework would treat this as parity, not a new feature: the local nav already ships "Return to top" as the dropdown's empty-state button, so the question is where that same action lives once the local nav is absent (≥80rem). Natural home is the aside outline footer, sharing the local nav's `scrollToTop` and the `returnToTopLabel` string rather than adding a second label and handler.
- **[#4522](https://github.com/vuejs/vitepress/issues/4522) — feat: add scroll to top button in VPDocAsideOutline component** (kind: pr-unmerged; demand: 👍3, 0c)
- 16-line implementation of [#4521](https://github.com/vuejs/vitepress/issues/4521) in `VPDocAsideOutline.vue`. Closed without discussion.
- Shows the minimal shape but hard-codes its own button and copy. A rework should hoist the return-to-top control into a shared piece used by both the local nav dropdown and the aside, so the two surfaces cannot drift.
- **[#2146](https://github.com/vuejs/vitepress/issues/2146) — Auto expand/collapse sections in page outline** (kind: issue-open; demand: 👍8, 2c)
- Deeply nested outlines are hard to scan; asks that child headings expand only while their parent is active. Includes light/dark mockups of the target behavior.
- Highest-demand outline behavior request. It interacts with the local nav because `VPDocOutlineItem` is shared verbatim by the dropdown, where vertical space is scarcest (the panel is height-capped by `--vp-vh`) — so collapsing benefits the dropdown more than the aside. A rework that generalizes active-anchor tracking to two consumers (see [#5090](https://github.com/vuejs/vitepress/issues/5090)/[#5091](https://github.com/vuejs/vitepress/issues/5091)) is the same change that makes "expand the active branch" implementable in both.
- **[#2297](https://github.com/vuejs/vitepress/issues/2297) — fix: better .has-aside condition** (kind: pr-unmerged; demand: 👍0, 0c)
- Recomputes `.has-aside` from rendered slots, `theme.carbonAds`, `getHeaders()` and the `layout`/`aside` configs, with unit tests for slot detection.
- Relevant because `hasAside` and `hasLocalNav` are decided by different rules today (`layout.ts` derives `hasAside` from config only, `hasLocalNav` from `headers.length`), so the two can disagree about whether a page has an outline at all. A rework should make outline presence one derived fact consumed by the aside, the local nav and the band handoff at 960/1280px; this PR's slot-and-config detection is the prior art for that predicate.
## Not viable
- **[#4735](https://github.com/vuejs/vitepress/issues/4735) — feat: add scroll support for the TOC** (kind: pr-unmerged; demand: 👍5, 1c) — Shipped differently in [#5377](https://github.com/vuejs/vitepress/issues/5377) (merged); `outline.ts:199` now calls `activeLink.scrollIntoView({ block: 'nearest' })`.
- **[#3351](https://github.com/vuejs/vitepress/issues/3351) — Keep aside marker visible** (kind: issue-open; demand: 👍0, 0c) — Resolved by merged [#5377](https://github.com/vuejs/vitepress/issues/5377) but never closed. Recommend closing rather than reworking.
- **[#3387](https://github.com/vuejs/vitepress/issues/3387) — Fix: Make the outline follow the page scroll** (kind: pr-unmerged; demand: 👍0, 2c) — Superseded by merged [#5377](https://github.com/vuejs/vitepress/issues/5377).
- **[#4634](https://github.com/vuejs/vitepress/issues/4634) — feat: active outline link scroll to page center** (kind: pr-unmerged; demand: 👍0, 3c) — Superseded by merged [#5377](https://github.com/vuejs/vitepress/issues/5377), which chose `block: 'nearest'` over centering.
- **[#4457](https://github.com/vuejs/vitepress/issues/4457) — feat(theme): add doc aside scroll spy** (kind: pr-unmerged; demand: 👍0, 1c) — Superseded by merged [#5377](https://github.com/vuejs/vitepress/issues/5377).
- **[#3901](https://github.com/vuejs/vitepress/issues/3901) — feat: Improve Sidebar and Aside Link Visibility on Mount and Route Change** (kind: pr-open; demand: 👍0, 0c) — Aside half superseded by merged [#5377](https://github.com/vuejs/vitepress/issues/5377); remaining half ([#3426](https://github.com/vuejs/vitepress/issues/3426), scroll active sidebar link into view) is sidebar scope.
- **[#2189](https://github.com/vuejs/vitepress/issues/2189) — fix(theme): ensure correct outline state** (kind: pr-unmerged; demand: 👍0, 1c) — Both symptoms gone from current `outline.ts`; bluwy closed as "most of the issue explained is now fixed".
- **[#2676](https://github.com/vuejs/vitepress/issues/2676) — fix(theme): outline marker flicks when navigating towards above** (kind: pr-unmerged; demand: 👍0, 3c) — Targets an activation path that no longer exists; marker positioning rewritten (`outline.ts:190-201`).
- **[#1631](https://github.com/vuejs/vitepress/issues/1631) — feat: add Set custom menu/return to top labels** (kind: pr-unmerged; demand: 👍0, 1c) — Already shipped: `sidebarMenuLabel` and `returnToTopLabel` live.
- **[#2455](https://github.com/vuejs/vitepress/issues/2455) — fix(theme): nav bar overflowed by aside when no sidebar** (kind: pr-unmerged; demand: 👍0, 7c) — Its issue [#2442](https://github.com/vuejs/vitepress/issues/2442) closed as completed; [#5397](https://github.com/vuejs/vitepress/issues/5397) rebuilt the geometry it patched.
- **[#1916](https://github.com/vuejs/vitepress/issues/1916) — fix 1915: aside always rendered even when outline is false** (kind: pr-unmerged; demand: 👍0, 1c) — Issue [#1915](https://github.com/vuejs/vitepress/issues/1915) closed as completed; handling in `layout.ts` today.
- **[#3194](https://github.com/vuejs/vitepress/issues/3194) — TOC aside height problem** (kind: issue-open; demand: 👍0, 0c) — Aside-only positioning complaint; no local-nav interaction.
- **[#5074](https://github.com/vuejs/vitepress/issues/5074) — feat(theme): add active link on mobile menu** (kind: pr-unmerged; demand: 👍0, 2c) — Patches `VPNavScreenMenuLink.vue`, which [#5397](https://github.com/vuejs/vitepress/issues/5397) deletes; [#5068](https://github.com/vuejs/vitepress/issues/5068) addressed on main by 0f0fe135.
- **[#1145](https://github.com/vuejs/vitepress/issues/1145) — Dynamic Outline** (kind: issue-not-planned; demand: 👍0, 6c) — Markdown/build-time concern, out of area.
- **[#2134](https://github.com/vuejs/vitepress/issues/2134) — Aside Location Order** (kind: issue-not-planned; demand: 👍0, 3c) — Already supported via `aside: 'left'`.

@ -0,0 +1,97 @@
# Navbar core — bar, mobile screen, dropdowns, overflow
Part of the [theme chrome audit](./README.md) · snapshot 2026-08-24 · baselined on [#5397](https://github.com/vuejs/vitepress/pull/5397) (`navbar-redesign`)
## Already solved by [#5397](https://github.com/vuejs/vitepress/issues/5397)
- **[#5097](https://github.com/vuejs/vitepress/issues/5097) — Allow overflow-x with horizontal scrolling in VPNav** (kind: pr-unmerged; demand: 👍0, 4c)
- Adds `overflow-x: auto` to the nav so a wide nav scrolls instead of being truncated above the hard-coded 960px media query; author later scoped it to the menu only and got stuck because dropdowns stopped floating once a scroll container existed.
- Solved by the measured `⋯` overflow menu plus removal of the fixed cluster breakpoint. The author's blocker (dropdown panels clipped by the scroll container) is structurally avoided because nothing becomes a scroll/clip ancestor. Downstream reports LuxDL/DocumenterVitepress.jl [#61](https://github.com/LuxDL/DocumenterVitepress.jl/issues/61) and [#118](https://github.com/LuxDL/DocumenterVitepress.jl/issues/118) are covered too.
- **[#2329](https://github.com/vuejs/vitepress/issues/2329) — feat(theme): use inert…** (kind: pr-unmerged; demand: 👍0, 0c)
- Solved: `Layout.vue` passes `:inert="isScreenOpen"` to `VPSkipLink`, `VPLocalNav`, `VPSidebar`, `VPContent`, `VPFooter`, with `isScreenOpen` module-scoped in `composables/nav.ts`.
- **[#1448](https://github.com/vuejs/vitepress/issues/1448) — fix(theme): Make the menu traversable only when the menu is visible** (kind: pr-unmerged; demand: 👍0, 3c)
- Solved on all three review counts: inert is the trap, Escape closes + restores focus to hamburger, screen accordions are real disclosures — which also closes the residual leak where the collapsed locale list kept links tabbable behind `overflow: hidden`.
- **[#1283](https://github.com/vuejs/vitepress/issues/1283) — fix: nav items overflow** (kind: pr-unmerged; demand: 👍0, 1c)
- One-line CSS attempt at [#1271](https://github.com/vuejs/vitepress/issues/1271); superseded by the priority-plus `⋯` menu.
- **[#5198](https://github.com/vuejs/vitepress/issues/5198) — fix(theme-default): stabilize horizontal layout across pages with/without vertical scrollbar** (kind: pr-open; demand: 👍0, 0c)
- Diagnoses a ~15px shift at ≥1440px on classic-scrollbar browsers: offsets computed from `100vw` inside `width: 100%` containers. Proposes `overflow-y: scroll` on `html`.
- The navbar's contribution is deleted: no more `margin-right: -100vw; padding-right: 100vw` bleed; `VPNavBar`/`VPLocalNav`/`VPContent` contain zero `100vw`. Residual is the already-decided [#5310](https://github.com/vuejs/vitepress/issues/5310) scrollbar-gutter question. (Sidebar sweep tracks the general problem under [#1054](https://github.com/vuejs/vitepress/issues/1054).)
## Fits a planned follow-up
- **[#3069](https://github.com/vuejs/vitepress/issues/3069) — feat(client): Add folding function to the navigation bar** (kind: pr-open; demand: 👍2, 0c)
- Adds a `collapsed` flag on dropdown menu groups so a group inside a flyout renders as an expandable section with a chevron.
- Fits the "nested dropdowns ([#3816](https://github.com/vuejs/vitepress/issues/3816))" follow-up. Requirement beyond the PR body: [#5397](https://github.com/vuejs/vitepress/issues/5397) made nested groups *render*, but `VPMenuGroup.vue` still emits a static `<p class="title">` — no toggle, no disclosure semantics. Needs a per-group `collapsed?: boolean` on `NavItemChildren` and a button/`aria-expanded` title, the same disclosure treatment `VPNavMenuGroup` already applies in the screen. (Note: the sidebar sweep flags this PR's implementation as reintroducing nested `role="button"` — absorb the feature, not the patch.)
- **[#4359](https://github.com/vuejs/vitepress/issues/4359) / [#4393](https://github.com/vuejs/vitepress/issues/4393) — Local nav dropdown misplaced without sidebar** (issue-open 5c / pr-open 16c)
- Fits the local-nav/sidebar rework. Requirement it adds: the local nav needs the same presence-derived column offset the navbar just gained (`--vp-nav-col-offset`), rather than reading `--vp-sidebar-width` unconditionally — `VPLocalNav.vue` still does `padding-left: var(--vp-sidebar-width)`.
- **[#2866](https://github.com/vuejs/vitepress/issues/2866) — feat: add middle slot in navbar** (kind: pr-open; demand: 👍1, 0c) and **[#2831](https://github.com/vuejs/vitepress/issues/2831) — Whether can add a slot in VPnavbar** (kind: issue-open; demand: 👍0, 1c)
- Request for a slot in the middle of the bar.
- Fits the "[#4000](https://github.com/vuejs/vitepress/issues/4000) component-collapse contract" follow-up. Requirement it adds: arbitrary slot content in the bar is invisible to the ResizeObserver in `composables/nav-overflow.ts`, which only measures registered menu items and the three cluster units. The [#4000](https://github.com/vuejs/vitepress/issues/4000) contract has to cover slot content, not just `component` nav items, or a middle slot silently re-introduces the crowding [#2842](https://github.com/vuejs/vitepress/issues/2842) fixed.
## New candidates for future rework
- **[#2081](https://github.com/vuejs/vitepress/issues/2081) — socialLinks items are not accessible** (issue-not-planned; 👍0, 1c) and **[#3014](https://github.com/vuejs/vitepress/issues/3014) — style(theme): configurable title attribute on custom social icons** (pr-unmerged; 👍0, 2c)
- Both declined with "put a `<title>` inside your SVG".
- Live gap: `VPSocialLink.vue` computes `:aria-label="ariaLabel ?? (typeof icon === 'string' ? icon : '')"` — a custom `{ svg }` icon with no `ariaLabel` gets `aria-label=""`, an unnamed link, rendered in the bar, drawer and `⋯` menu. The unified `VPNavSocialLinks` gives one place to require or derive a name (fall back to hostname, or warn in dev).
- **[#3086](https://github.com/vuejs/vitepress/issues/3086) — Navbar visible on print when navbar=false** (issue-open; 👍0, 0c), **[#2913](https://github.com/vuejs/vitepress/issues/2913) — Hide some UI elements when printing** (pr-open; 👍0, 7c), **[#3123](https://github.com/vuejs/vitepress/issues/3123) — don't print navbar when disabled in frontmatter (narrow screens)** (pr-unmerged; 👍0, 2c)
- The theme has no `@media print` rules at all. With the bar now one surface with a single state rule and one shared geometry, a print block is a small self-contained addition rather than the per-mode selector sprawl that made [#2913](https://github.com/vuejs/vitepress/issues/2913)'s review stall on scope.
- **[#3383](https://github.com/vuejs/vitepress/issues/3383) — Fix transparent nav bar** (kind: pr-unmerged; demand: 👍0, 5c)
- Proposed a `transparentNavBar` frontmatter flag for `layout: page`. Declined by kiaking: styling belongs in CSS variables, not config — but he counter-proposed that the bar should be transparent by default on `page` when there is no sidebar and no local nav.
- The decline reason is now fully satisfiable: `--vp-nav-home-bg-color` exists and transparent-until-scroll is a single state rule. kiaking's counter-proposal is the unaddressed part and is now a one-condition change to that rule.
- **[#2085](https://github.com/vuejs/vitepress/issues/2085) — navbar inconsistent behaviour** (kind: issue-open; demand: 👍0, 6c)
- Three different hover treatments across nav element types: `VPFlyout` hovers to `--vp-c-brand-1`, `VPSocialLink` to `--vp-c-text-1`, `VPNavMenuLink` has its own rule, `VPNavBarTitle` has no hover rule at all.
- Now that all are siblings in one component set, a shared nav-interactive token is a contained change. (A11y sweep adds: the thread's WCAG carve-out is met by the missing `input:focus-visible` outline defect.)
- **[#4347](https://github.com/vuejs/vitepress/issues/4347) — Language selector points to missing URLs** (kind: issue-open; demand: 👍0, 0c)
- `VPNavTranslations.vue` calls `useLangs({ linkToCorrespondingPage: true })` once and feeds all three renderings, so an existence check in `composables/langs.ts` fixes every surface at once. Needs build-time knowledge of which locale pages exist.
- **[#4364](https://github.com/vuejs/vitepress/issues/4364) — Links in the Nav bar do not support rewrites** (kind: issue-open; demand: 👍1, 0c)
- A `rewrites` entry is honored by hero actions, feature cards and in-page links, but not nav links.
- [#5397](https://github.com/vuejs/vitepress/issues/5397) routed every nav href through one composable, `useNavItemLink` in `composables/nav.ts`, consumed by `VPNavMenuLink` and `VPMenuLink` in the bar, drawer, dropdowns and `⋯` menu. Rewrite-aware normalization now has exactly one insertion point.
- **[#4141](https://github.com/vuejs/vitepress/issues/4141) — Allow SVG logo to be inlined** (kind: issue-open; demand: 👍1/tot 2, 1c)
- Inline the logo as `<svg>` rather than `<img>` so CSS can theme it (dark-mode-adaptive single file).
- `VPNavBarTitle.vue` renders `<VPImage>` unconditionally. Logo-shaped counterpart to the surface theming pass; `--vp-nav-logo-height` establishes the precedent. Related: [#1742](https://github.com/vuejs/vitepress/issues/1742) (not planned, same request).
- **[#2706](https://github.com/vuejs/vitepress/issues/2706) — nav-bar-after slot below the bar** (kind: issue-open; demand: 👍0, 2c)
- A slot *below* the bar (not inside it) for a full-bleed banner. Doesn't touch overflow measurement, but must compose with the full-bleed background surface and `--vp-nav-height` consumers. Natural home: `VPNav.vue` between the bar and `VPLocalNav`.
- **[#522](https://github.com/vuejs/vitepress/issues/522) — NavBar can not be hidden** (kind: issue-not-planned; demand: 👍0, 5c)
- Closed with "use `display: none`", but the thread surfaced two real gaps: per-page hiding isn't possible with CSS, and `VPContent` keeps its `padding-top` for a bar that isn't there. kiaking's last word was "Yeah I think we can add this feature... open a new issue" — nobody did.
- `frontmatter.navbar !== false` already gates `VPNav`, and `--vp-nav-height` is now the single geometry input consumed by `VPContent`, `VPNavScreen` and the local nav, so zeroing it per-page is one rule. Pairs with [#3086](https://github.com/vuejs/vitepress/issues/3086)/[#2913](https://github.com/vuejs/vitepress/issues/2913). (Local-nav sweep: the `.hide-nav` → `--vp-nav-height: 0px` override currently lives on the layout div where the local nav's body-appended probe can't see it.)
- **[#3407](https://github.com/vuejs/vitepress/issues/3407) — Add separator/delimiter between nav links** (kind: issue-open; demand: 👍0, 0c)
- `{ type: 'separator' }` as a nav item. `VPNavMenu.vue` already branches on item shape, so a fourth branch is cheap — but the overflow engine must treat separators as zero-priority: `computeNavFit` collapses items right-to-left by index, and a separator stranded at the visible/collapsed boundary would render as a dangling rule.
- **[#1764](https://github.com/vuejs/vitepress/issues/1764) — Navbar is not sticky in mobile breakpoint** (kind: issue-not-planned; demand: 👍1, 2c)
- Declined as design choice ("only the secondary nav is sticky"). Unanswered rebuttal: on mobile home there is no secondary nav, so nothing is sticky at all.
- Still exactly true — `VPNav.vue` is `position: relative`, `fixed` only at `min-width: 60rem`. With one background surface and one state rule, the change is a media-query edit rather than a background/divider cascade.
## Not viable
- **[#4413](https://github.com/vuejs/vitepress/issues/4413) — Theme switcher should be more clear and obvious** (issue-not-planned; 👍1, 3c) — Declined on product grounds; [#5397](https://github.com/vuejs/vitepress/issues/5397) already fixed the concrete half (accessible name no longer contradicts state).
- **[#4978](https://github.com/vuejs/vitepress/issues/4978) — gap between VPNavBar and VPNavScreen when partially scrolled** (pr-unmerged; 👍0, 2c) — Target [#4972](https://github.com/vuejs/vitepress/issues/4972) fixed by merged [#5369](https://github.com/vuejs/vitepress/issues/5369).
- **[#5074](https://github.com/vuejs/vitepress/issues/5074) — active link on mobile menu** (pr-unmerged; 👍0, 2c) — [#5068](https://github.com/vuejs/vitepress/issues/5068) completed via [#5086](https://github.com/vuejs/vitepress/issues/5086); current-link marking in [#5395](https://github.com/vuejs/vitepress/issues/5395); `VPMenuLink` already emits `active` + `aria-current`.
- **[#2260](https://github.com/vuejs/vitepress/issues/2260) — close dropdown menus after item click** (pr-unmerged; 👍0, 3c) — [#2132](https://github.com/vuejs/vitepress/issues/2132) completed; `VPFlyout` closes on `route.path` change.
- **[#2741](https://github.com/vuejs/vitepress/issues/2741) — properly re-apply navbar classes** (pr-unmerged; 👍0, 0c) — Target [#2364](https://github.com/vuejs/vitepress/issues/2364) completed.
- **[#2455](https://github.com/vuejs/vitepress/issues/2455) — nav bar overflowed by aside when no sidebar** (pr-unmerged; 👍0, 7c) — Target [#2442](https://github.com/vuejs/vitepress/issues/2442) completed.
- **[#4502](https://github.com/vuejs/vitepress/issues/4502) — Allow clicks on custom navbar** (pr-unmerged; 👍0, 3c) — `pointer-events: none` on `VPNav` is deliberate (keeps sidebar scrollbar top clickable), unchanged after [#5397](https://github.com/vuejs/vitepress/issues/5397).
- **[#2597](https://github.com/vuejs/vitepress/issues/2597) — css variable for navbar logo height** (pr-unmerged; 👍0, 0c) — Shipped: `--vp-nav-logo-height` via merged [#2644](https://github.com/vuejs/vitepress/issues/2644).
- **[#1631](https://github.com/vuejs/vitepress/issues/1631) / [#2259](https://github.com/vuejs/vitepress/issues/2259) — label config PRs** — Shipped (`returnToTopLabel`, `darkModeSwitchLabel`, `lightModeSwitchTitle`, `darkModeSwitchTitle`) alongside [#5397](https://github.com/vuejs/vitepress/issues/5397)'s new labels.
- **[#2747](https://github.com/vuejs/vitepress/issues/2747) — langMenuLabel does nothing** (issue-not-planned; 👍0, 1c) — Working as designed (aria-label, not visible text).
- **[#5129](https://github.com/vuejs/vitepress/issues/5129) — Add force-light appearance** (pr-unmerged; 👍0, 3c) — `appearance: false` is equivalent; no counter-repro produced.
- **[#5170](https://github.com/vuejs/vitepress/issues/5170) — normalize navbar search keycaps** (pr-unmerged; 👍0, 0c) — Target [#2885](https://github.com/vuejs/vitepress/issues/2885) completed.
- **[#4917](https://github.com/vuejs/vitepress/issues/4917) — Frequent redrawing of navigation bar icons** (issue-not-planned; 👍0, 4c) — Traced to the reporter's own clock widget.
- **[#2912](https://github.com/vuejs/vitepress/issues/2912) — Hide JS-required features if JS disabled** (pr-open; 👍0, 9c) — kiaking argued for a separate JS-free theme; [#5397](https://github.com/vuejs/vitepress/issues/5397) doesn't worsen it (`nav-overflow` is all-visible during SSR; `VPNavBarExtra` is `v-if="hasContent"`), degrades cleanly.
- **[#1008](https://github.com/vuejs/vitepress/issues/1008) / [#4061](https://github.com/vuejs/vitepress/issues/4061) — Multiple/secondary navbars** — kiaking's scope decline stands.
- **[#1216](https://github.com/vuejs/vitepress/issues/1216) — export VPSocialLinks** (pr-unmerged; 👍0, 1c) — Deliberate API-surface decision, unrelated to the redesign.
- **[#1089](https://github.com/vuejs/vitepress/issues/1089), [#1682](https://github.com/vuejs/vitepress/issues/1682), [#1550](https://github.com/vuejs/vitepress/issues/1550), [#1742](https://github.com/vuejs/vitepress/issues/1742)** (issue-not-planned) — Obsolete or redirected: first two answered by i18n work ([#631](https://github.com/vuejs/vitepress/issues/631)); [#1550](https://github.com/vuejs/vitepress/issues/1550) duplicate of known [#109](https://github.com/vuejs/vitepress/issues/109); [#1742](https://github.com/vuejs/vitepress/issues/1742) superseded by [#4141](https://github.com/vuejs/vitepress/issues/4141).

@ -0,0 +1,27 @@
# PR #1273 — adopted vs not implemented
Part of the [theme chrome audit](./README.md) · snapshot 2026-08-24 · baselined on [#5397](https://github.com/vuejs/vitepress/pull/5397) (`navbar-redesign`)
fvsch's "limit damage of nav items overflowing navbar" (Sep 2022, closed Oct 2022 when kiaking deferred the whole topic via the [#1271](https://github.com/vuejs/vitepress/issues/1271) decision comment). Six files, CSS-only, with inline review notes explaining each choice. Compared change-by-change against `navbar-redesign`.
## Adopted by [#5397](https://github.com/vuejs/vitepress/issues/5397) (or superseded structurally)
- **`min-height` + flex centering instead of the `line-height` trick on menu links.** `VPNavMenuLink` now has `display: flex; align-items: center; min-height: var(--vp-nav-height); line-height: 1.5` — exactly [#1273](https://github.com/vuejs/vitepress/issues/1273)'s `VPNavBarMenuLink` change. This is the part the PR body credits.
- **Menu vertical alignment** (`align-items` on the menu container): equivalent effect via the link's own flex centering; spacing from the search button comes from the links' `0 0.75rem` padding ([#1273](https://github.com/vuejs/vitepress/issues/1273) used `padding-inline-start: 12px` — same 12px, different carrier).
- **The 768–960px "almost guaranteed not to fit" band** that [#1273](https://github.com/vuejs/vitepress/issues/1273) explicitly declared out of scope ("I have a few ideas in mind... beyond the scope of this PR") is solved wholesale: the overflow engine activates at ≥48rem (=768px), covering exactly the band fvsch couldn't.
- **Title border accounting** (border adding 1px to title height on small screens): superseded — [#5397](https://github.com/vuejs/vitepress/issues/5397) deleted the title border-bottom entirely.
- **`transition: border-color` removal** (white→dark border flash when resizing across breakpoints in dark mode): superseded — the border is gone. Note the same *class* of issue exists in principle for the new surface: `::before` background-color transitions 0.25s, and crossing the 60rem breakpoint on a home page flips transparent↔solid through that transition. That one is a deliberate fade between surface states rather than a divider flashing the wrong color, but it's the same "transition fires on media flip" mechanism fvsch flagged.
- **The +1px border accounting** on the bar and `VPNavScreen`'s top offset: structurally moot — the bar box has no border; the divider is its own in-flow element after the content wrapper, so full-height children cannot paint over it, and the screen-open state deliberately keeps the divider line visible as the screen's top rule.
## Handled in [#1273](https://github.com/vuejs/vitepress/issues/1273) but NOT implemented (the deltas)
1. **`VPFlyout` still uses the big line-height trick.** [#1273](https://github.com/vuejs/vitepress/issues/1273) changed the flyout button text to `line-height: 20px` + `padding: 4px 12px`, with centering from the button's flex (his note: "Button styles already center the button's contents, so we don't need to use the big line-height trick here anyway"). On the branch, `VPFlyout.vue` `.text` still carries `line-height: var(--vp-nav-height)`. Centering does come from the button's flex, so the declaration is redundant today — but it's the same wrapped-label height bomb that was removed from menu links, left armed in the sibling component. The PR body's "the approach from [#1273](https://github.com/vuejs/vitepress/issues/1273)" claim is true for menu links and not yet true for flyout buttons. One-line fix (drop the line-height or set a normal one).
2. **Icon shrink guards.** [#1273](https://github.com/vuejs/vitepress/issues/1273) added `flex: none` to the flyout chevron after measuring it compressed to 9px under flex pressure, with the stated rule of thumb: SVG/icon flex children always get `flex: none` or `flex-shrink: 0`. The branch has no shrink guard on `.text-icon` / `.icon` in `VPFlyout` (or on icon spans elsewhere in the bar). The overflow engine prevents *steady-state* squeeze ≥48rem, but transient squeeze between a resize and the ResizeObserver callback, and any squeeze in non-engine contexts, still shrinks unguarded icons. Cheap hardening, aligned with the redesign's "no layout-dependent magic" stance.
3. **Bar growth under over-tall or wrapping content (`min-height` on the bar below the desktop breakpoint).** This was [#1273](https://github.com/vuejs/vitepress/issues/1273)'s core damage-control mechanism and its WCAG 1.4.4 (Resize text, AA) argument: content taller than the bar must expand the bar, not overflow it and obscure the page. [#5397](https://github.com/vuejs/vitepress/issues/5397) chose the other prong — prevent wrap entirely (`white-space: nowrap`) and collapse by measurement — which resolves 1.4.4 for all theme-managed content, since text-only zoom grows item widths and the engine collapses them. What's not carried: **custom slot content** (`nav-bar-content-before/after`). The bar, container, and content-body are all fixed `height: var(--vp-nav-height)` at every width, so slot content that wraps or is intrinsically taller than the bar paints over page content instead of growing the bar — the exact failure [#1273](https://github.com/vuejs/vitepress/issues/1273) existed to stop, now confined to user-injected content. This folds directly into the planned [#4000](https://github.com/vuejs/vitepress/issues/4000) opt-in collapse contract, which the navbar-core audit already extends to slot content for *width* (slot content is invisible to the overflow engine); [#1273](https://github.com/vuejs/vitepress/issues/1273) adds the *height* half of the same contract question: measure-and-collapse, grow, or document fixed-height as a constraint — decide once for slot content in both axes.
## Context worth keeping
- Closure was not on the merits: kiaking closed it pointing at the [#1271](https://github.com/vuejs/vitepress/issues/1271) decision comment (the same thread whose only unrejected option — priority-plus — is what [#5397](https://github.com/vuejs/vitepress/issues/5397) ships). fvsch is already credited as a co-author on [#5397](https://github.com/vuejs/vitepress/issues/5397).
- fvsch's inline notes are a quality bar for the follow-ups: every declaration justified against a measured failure (9px icon, 57px title, dark-mode border flash). The two one-line deltas (1) and (2) are immediately implementable in [#5397](https://github.com/vuejs/vitepress/issues/5397) if desired; (3) is a design decision for the [#4000](https://github.com/vuejs/vitepress/issues/4000) follow-up.

@ -0,0 +1,139 @@
# Sidebar — drawer, groups, scroll, data layer
Part of the [theme chrome audit](./README.md) · snapshot 2026-08-24 · baselined on [#5397](https://github.com/vuejs/vitepress/pull/5397) (`navbar-redesign`)
## Already solved by [#5397](https://github.com/vuejs/vitepress/issues/5397)
- **[#3517](https://github.com/vuejs/vitepress/issues/3517) — accessibility: interactive controls should not be nested** (kind: issue-open; demand: 👍0, 1c)
- The collapsible group header was a `tabindex="0"` / `role="button"` row wrapping a second `role="button"` caret; axe reports ~102 failing nodes per page and ~70 redundant tab stops on a 326-page site.
- Solved in the branch, but by `ed2bfb26 fix(theme): render sidebar group toggles as native buttons` on `main` (which [#5397](https://github.com/vuejs/vitepress/issues/5397) sits on), not by [#5397](https://github.com/vuejs/vitepress/issues/5397)'s own diff. `VPSidebarItem.vue` now renders one native `<button type="button" :aria-expanded>` caret, the row keeps a mouse-only click handler, and `sectionTag` only emits `<section>` when a heading exists. The commenter's separate Space-key ask is also resolved for free by the native button. Safe to close.
- **[#5251](https://github.com/vuejs/vitepress/issues/5251) — fix: avoid nested sidebar controls** (kind: pr-open; demand: 👍0, 0c)
- Makes non-link carets decorative and keeps the caret as the toggle for linked groups; fixes [#3517](https://github.com/vuejs/vitepress/issues/3517) with e2e coverage.
- Superseded by `ed2bfb26`, which took the inverse split (caret is always the single control). Close as obsolete.
- **[#5371](https://github.com/vuejs/vitepress/issues/5371) — fix(theme): remove invalid role and tabindex from VPSidebarItem wrapper** (kind: pr-unmerged; demand: 👍0, 0c)
- Same nested-control removal, minimal diff.
- Already absorbed: `ed2bfb26` says `closes #5371` and credits the author as co-author.
- **[#3802](https://github.com/vuejs/vitepress/issues/3802) — interactive controls are not nested [#3517]** (kind: pr-unmerged; demand: 👍0, 1c)
- Original 2024 attempt at the same one-control-per-header rule.
- Superseded by `ed2bfb26`; its rationale is what shipped.
## Fits a planned follow-up
- **[#3392](https://github.com/vuejs/vitepress/issues/3392) — feat(theme): add 'inert' attribute to prevent unnecessary traversal of hidden content** (kind: pr-open; demand: 👍0, 1c)
- Adds a shared `composables/inert.ts` exposing `isScreenOpen` / `isSidebarOpen` / `isSidebarVisible` and threads `inert` into nav, localNav, sidebar, content, footer and skip-link; also adds a focus trap to the outline popup.
- Fits the sidebar rework (the "closed sidebar staying in the tab order" item). [#5397](https://github.com/vuejs/vitepress/issues/5397) wired inert for `isScreenOpen` only. Concretely the rework must: (a) give closed `.VPSidebar` `visibility: hidden` — the base rule only sets `opacity: 0; transform: translateX(-100%)`, and `visibility: visible` appears only under `.open` and the `min-width: 60rem` block, so every sidebar link is focusable while the drawer is shut; (b) mark the page inert behind the open drawer and trap focus, generalizing [#5397](https://github.com/vuejs/vitepress/issues/5397)'s `isScreenOpen` inert to a shared control; (c) fix `VPSidebar.vue`'s open-focus, which puts `ref="navEl"` on the `<aside>` while `tabindex="-1"` is on the inner `<nav>`, so `navEl.value?.focus()` is a no-op today. Note the reviewer comment "Sidebar not woking..." — the draft's sidebar branch is unfinished.
- **[#2329](https://github.com/vuejs/vitepress/issues/2329) — feat(theme): use inert to avoid traverse menus and content with keyboard** (kind: pr-unmerged; demand: 👍0, 0c)
- Earlier inert pass over `Layout.vue`, `VPSkipLink.vue` and `nav.ts`.
- Same follow-up. Its load-bearing datum: it states [#1332](https://github.com/vuejs/vitepress/issues/1332) ("drawer should not be traversable when not shown") was closed by [#1491](https://github.com/vuejs/vitepress/issues/1491) but regressed and is not fixed in current versions — [#1332](https://github.com/vuejs/vitepress/issues/1332) is still marked completed, so the rework should reopen or re-verify it rather than trust the closed state.
- **[#4359](https://github.com/vuejs/vitepress/issues/4359) — Local navigation dropdown misplaced without sidebar** (kind: issue-open; demand: 👍0, 5c)
- With no sidebar the "On this page" dropdown is offset right by the sidebar width. Maintainer's stated fix: `--vp-sidebar-width` should be 0 when there is no sidebar.
- Fits the local-nav rework. [#5397](https://github.com/vuejs/vitepress/issues/5397) touched `VPLocalNavOutlineDropdown.vue` only for disclosure semantics; line 172 still reads `left: calc(var(--vp-sidebar-width) + 2rem)` unconditionally, and `VPLocalNav.vue:90` has the same unconditional `padding-left`. The rework must make `--vp-sidebar-width` resolve to 0 without a sidebar (or gate on a `has-sidebar` class) — the same one-variable discipline [#5397](https://github.com/vuejs/vitepress/issues/5397) applied when it deleted the five duplicated sidebar-width calcs from the navbar.
- **[#4393](https://github.com/vuejs/vitepress/issues/4393) — fix(components): Local navigation location error** (kind: pr-open; demand: 👍0, 16c)
- The fix for [#4359](https://github.com/vuejs/vitepress/issues/4359), reworked on review feedback to drive `left` from a `has-sidebar` class; explicitly LGTM'd by yuyinws and cc'd to the maintainer, then stalled.
- Same follow-up as [#4359](https://github.com/vuejs/vitepress/issues/4359). The rework should adopt the class-driven approach and credit the author; there is no technical objection on the thread, only inactivity.
- **[#3804](https://github.com/vuejs/vitepress/issues/3804) — sidebar: use native <details> for collapsible groups** (kind: issue-open; demand: 👍0, 1c)
- Asks for native `<details>` for group toggles: standard keyboard behavior, better announcement, less custom JS.
- Fits the sidebar rework. Delta after `ed2bfb26`: the Space-key half is already fixed (native `<button>`), so what remains is purely the structural ask. [#5397](https://github.com/vuejs/vitepress/issues/5397)'s progressive-enhancement list already commits to `details`/`summary` with `::details-content` for the *nav drawer* accordions — the sidebar rework should extend that same decision to sidebar groups, which is where the request originated.
- **[#3806](https://github.com/vuejs/vitepress/issues/3806) — sidebar uses native <details> for collapsible groups [#3804. also [#3517](https://github.com/vuejs/vitepress/issues/3517)]** (kind: pr-open; demand: 👍0, 12c)
- Real implementation, +78/−99 across 3 files, unblocked by the Vue fix it was waiting on and repeatedly offered by the author, who has been asking for review since 2024.
- Same follow-up; this is the ready-made starting point. One requirement it surfaces that the PR body does not cover: the maintainer explicitly rejected the [#3805](https://github.com/vuejs/vitepress/issues/3805) half ("clicking a linked group heading should still uncollapse the section"), so the rework must keep click-on-heading toggling as-is when moving to `<details>` — the `<summary>` cannot swallow the link's navigation.
- **[#5090](https://github.com/vuejs/vitepress/issues/5090) — Theme suggestion: mobile TOC active highlight** (kind: issue-open; demand: 👍0, 0c)
- The mobile "On this page" dropdown does not mark the currently-active heading, unlike the desktop outline.
- Fits the local-nav rework. `VPLocalNavOutlineDropdown.vue` renders `VPDocOutlineItem` with no active state and carries `data-allow-mismatch="style"`; the rework's SSR-determinism pass over the local nav should replace that band-aid and add the active marker in the same change.
- **[#5091](https://github.com/vuejs/vitepress/issues/5091) — feat: mobile TOC active highlight** (kind: pr-open; demand: 👍0, 0c)
- The implementation of [#5090](https://github.com/vuejs/vitepress/issues/5090).
- Same follow-up; fold into the local-nav rework rather than merging standalone, since the rework rewrites the component it patches.
- **[#4330](https://github.com/vuejs/vitepress/issues/4330) — Open sidebar with touch navigation (swipe) (bug in useSidebar?)** (kind: issue-not-planned; demand: 👍0, 3c)
- Asks for VuePress-style swipe-to-open, and reports that `useSidebar` cannot be used to open/close the drawer from a custom layout at all.
- The decline reason (bluwy: swipe conflicts with mobile back-gestures and is hard to trigger) still holds for the gesture, but the *second* half was never addressed and a ground-up drawer rework invalidates it: `useSidebarControl`'s `open`/`close`/`toggle` live in a module-level ref that is not exported from the theme entry, so both commenters resort to `document.querySelector('.VPBackdrop.backdrop').click()`. The rework should expose the drawer's open/close control publicly, which closes the actionable part regardless of the swipe verdict.
## New candidates for future rework
- **[#2257](https://github.com/vuejs/vitepress/issues/2257) — Highlight active sidebar item when child page is loaded** (kind: issue-open; demand: 👍9, 7c)
- A page not listed in the sidebar leaves no item highlighted and no section expanded; five separate commenters converge on wanting `activeMatch` for sidebar items, as nav items already have.
- Highest-demand active-link issue in the area. The rework's active-link logic should accept a per-item `activeMatch` (and/or prefix-match a parent when no exact link matches). `useSidebarItemControl` currently calls `isActive(relativePath, hash, item.link)` exact-only, so this is a targeted change at the point the rework already has open. Related closed-as-duplicate: [#1565](https://github.com/vuejs/vitepress/issues/1565).
- **[#4345](https://github.com/vuejs/vitepress/issues/4345) — Auto-anchor the sidebar to the opened page** (kind: issue-open; demand: 👍3, 1c)
- Opening a deep link leaves the sidebar scrolled to the top, hiding the active item.
- Core of the "auto-scroll to the active item" requirement. Incorporate as first-class scroll management in the reworked sidebar: scroll the active item into view on mount and on route change, with `scroll-behavior` respecting reduced motion. The commenter's workaround (`querySelector('#VPSidebarNav div.is-link.is-active.has-active').scrollIntoView`) shows the state is already rendered — only the scroll is missing.
- **[#4296](https://github.com/vuejs/vitepress/issues/4296) — Opening or navigating to sidebar links should focus/scroll to the sidebar item** (kind: issue-open; demand: 👍2, 3c)
- Same ask framed as VS Code's `explorer.autoReveal`, with two reproductions on vitepress.dev itself.
- Same rework item as [#4345](https://github.com/vuejs/vitepress/issues/4345); adds the requirement that it fire on in-site navigation (clicking a nav link that changes the sidebar), not just first load.
- **[#3426](https://github.com/vuejs/vitepress/issues/3426) — Automatically scroll to active page on sidebar** (kind: issue-open; demand: 👍0, 0c)
- Duplicate framing of the same auto-scroll ask; author offers to contribute.
- Fold into the same rework item. Also closed-as-duplicate [#4579](https://github.com/vuejs/vitepress/issues/4579) carries 👍2 for this behavior, so real demand is higher than any single record shows.
- **[#2881](https://github.com/vuejs/vitepress/issues/2881) — an option to auto-collapse sidebar group + scroll sidebar item into view** (kind: issue-open; demand: 👍1, 0c)
- Pairs the auto-scroll ask with auto-collapsing other groups.
- The rework should treat scroll-into-view and group auto-collapse as one behavior, since collapsing changes the scroll target's position — doing them independently produces a wrong final scroll offset.
- **[#3654](https://github.com/vuejs/vitepress/issues/3654) — feat: scroll active sidebar link into view on page load** (kind: pr-open; demand: 👍6, 0c)
- Minimal +7/−1 implementation in `VPSidebar.vue`; highest-👍 open sidebar PR.
- The author themselves flags the two gaps a rework must close: it uses `querySelector` rather than a template ref, and it does not re-run on in-page hash navigation.
- **[#3901](https://github.com/vuejs/vitepress/issues/3901) — feat: Improve Sidebar and Aside Link Visibility on Mount and Route Change** (kind: pr-open; demand: 👍0, 0c)
- Broader take (+44/−7) covering both sidebar and aside, on mount and on route change.
- Opened only because the author could not push to [#3654](https://github.com/vuejs/vitepress/issues/3654). Use it as the reference for the route-change half; it also targets [#3351](https://github.com/vuejs/vitepress/issues/3351) (aside marker scrolling out of view), which the rework can cover with the same mechanism.
- **[#5194](https://github.com/vuejs/vitepress/issues/5194) — feat: Auto-anchor the sidebar to the active item** (kind: pr-open; demand: 👍0, 1c)
- The most complete attempt (+69/−2): centers the active link, re-centers on resize, and suppresses auto-positioning once the user scrolls the sidebar manually until the route, sidebar content, or drawer state changes. Links [#3426](https://github.com/vuejs/vitepress/issues/3426), [#4296](https://github.com/vuejs/vitepress/issues/4296), [#4345](https://github.com/vuejs/vitepress/issues/4345), [#4579](https://github.com/vuejs/vitepress/issues/4579).
- This is the behavioral spec the rework should adopt wholesale — the manual-scroll-wins rule is the part naive implementations get wrong, and it doubles as the "preserved scroll position" requirement.
- **[#4211](https://github.com/vuejs/vitepress/issues/4211) — [Feature Proposal] Expand only active sidebar group** (kind: issue-open; demand: 👍0, 0c)
- Opt-in accordion mode: navigating opens the active group and closes the others, while an explicit toggle click affects only that group. Author has a working `enhanceApp` implementation and offers to PR.
- The rework should hoist collapse state out of per-item local `ref`s into shared state so one group can close another; `useSidebarItemControl` currently owns `collapsed` privately per item, which makes an accordion mode impossible without exactly the rework being planned.
- **[#3441](https://github.com/vuejs/vitepress/issues/3441) — How to make multiple sidebars expand only one at a time** (kind: issue-open; demand: 👍0, 1c)
- Same accordion-exclusivity request, bumped by another user as "a very reasonable feature, should not be staled".
- Same shared-collapse-state requirement as [#4211](https://github.com/vuejs/vitepress/issues/4211); count them as one feature with two requesters.
- **[#4683](https://github.com/vuejs/vitepress/issues/4683) — 侧边栏最大层级是多少 (max sidebar depth)** (kind: issue-not-planned; demand: 👍0, 3c)
- Sidebar silently stops rendering past 5 levels; the reporter has a 9-level tree, and the maintainer's answer was "re-organize your content" plus a `patch-package` diff.
- The decline reason is a horizontal-space argument, but the cap is not a space decision — it is `v-if="depth < 5"` in `VPSidebarItem.vue` plus `textTag` computing `h${depth + 2}`, i.e. the depth limit exists because heading levels run out at `h6`. A rework that decouples the visual nesting from the heading hierarchy (a flat `nav` + `aria-level`, or `<details>` per [#3806](https://github.com/vuejs/vitepress/issues/3806)) removes the cap as a side effect, which invalidates the "just re-organize" answer. The fact that a patch diff was handed out is evidence the limit is arbitrary.
- **[#563](https://github.com/vuejs/vitepress/issues/563) — Deep nested side bar title isn't indented.** (kind: issue-not-planned; demand: 👍0, 3c)
- Indentation breaks past 4 levels of nesting.
- Gold: declined by kiaking with "sidebar has no more nested structure in `theme-next`" — a reason that is now completely invalid, since multi-level nesting shipped in [#851](https://github.com/vuejs/vitepress/issues/851)/[#1835](https://github.com/vuejs/vitepress/issues/1835) and the same in-thread exchange confirms it. The indent rules are still per-level hardcoded selectors (`.level-2 … .level-5`), so the rework should derive indentation from depth rather than enumerating levels, which fixes [#563](https://github.com/vuejs/vitepress/issues/563) and [#4683](https://github.com/vuejs/vitepress/issues/4683) together.
- **[#4841](https://github.com/vuejs/vitepress/issues/4841) — getSidebar's matching logic is buggy** (kind: issue-open; demand: 👍0, 3c)
- Multi-sidebar keys are prefix-matched without normalizing a trailing slash, so `/api-examples.md` matches the `/a` sidebar and `/api-b/a.md` matches `/api/`.
- Multi-sidebar matching is in scope for the rework. The reporter's argument is the strong one and unrebutted: the public type is `SidebarMulti { [path: string]: … }` documented as a directory, so the rework should normalize keys to directory boundaries rather than requiring users to remember a trailing slash. The maintainer's only reply was the workaround, not a design defense.
- **[#4842](https://github.com/vuejs/vitepress/issues/4842) — fix(theme): fix getSidebar's buggy logic when supporting subtree-lifting** (kind: pr-open; demand: 👍0, 1c)
- Eight-line fix for [#4841](https://github.com/vuejs/vitepress/issues/4841) with a reproduction repo.
- Adopt into the rework's data layer; verify it against the docs site's own config, since the change tightens matching and could shift which sidebar a page resolves to.
- **[#3621](https://github.com/vuejs/vitepress/issues/3621) — fix(VPSidebarItem): use depth and index as the key** (kind: pr-unmerged; demand: 👍0, 2c)
- Attempt to fix collapsed groups not expanding on navigation by keying items on depth+index; two reviewers reported it did not work.
- Worth absorbing as a structural requirement even though the patch failed. Today `VPSidebarItem` keys children on `:key="i.text"` (duplicate texts collide) while `VPSidebar.vue` separately bumps a `key` on any deep change to `sidebarGroups`, remounting the entire tree. That blunt remount is what will destroy preserved scroll position and collapse state once the rework adds them, so the rework needs stable per-item identity instead of a whole-tree key bump.
- **[#1037](https://github.com/vuejs/vitepress/issues/1037) — Allow footer compatible with sidebar** (kind: issue-open (reopened); demand: 👍7, 5c)
- Footer and sidebar cannot be shown together; users need copyright/legal text on doc pages and are patching it in with `display: block !important`.
- Sidebar/layout coupling. The maintainer already conceded the shape ("make it optional to enable footer using frontmatter or some theme config"), so the rework only needs to add the opt-in and make the footer respect the sidebar column offset.
- **[#4532](https://github.com/vuejs/vitepress/issues/4532) — feat(theme): allow footer and sidebar to be displayed at the same time** (kind: pr-open; demand: 👍6, 2c)
- Implements exactly the maintainer's requested shape: `footer.showWithSidebar` plus a per-page `footer` frontmatter override.
- Ready to absorb; the only outstanding review note is that the footer's horizontal divider should span the full browser width — the same full-bleed surface problem [#5397](https://github.com/vuejs/vitepress/issues/5397) already solved for the navbar, so the rework can apply the identical technique.
- **[#3071](https://github.com/vuejs/vitepress/issues/3071) — Notion-style "Lock sidebar open" / "Close sidebar"** (kind: issue-open; demand: 👍0, 4c)
- After clarification, the ask is a user-initiated sidebar collapse on *large* viewports, not the existing small-viewport drawer.
- Sidebar sizing. A rework that already owns `--vp-sidebar-width` and its coupling to nav and content can add a collapsed desktop state as a variable flip plus a persisted preference, rather than the bolt-on it would be today.
- **[#5105](https://github.com/vuejs/vitepress/issues/5105) — feat(theme): add sidebar collapse functionality** (kind: pr-open; demand: 👍1, 2c)
- Desktop collapse across 9 files (+330/−37), touching `VPNavBar`, `VPNavBarTitle`, `VPNavBarSearch`, `VPContent`, `VPLocalNav`, `VPSidebar` and `sidebar.ts`. Links [#4669](https://github.com/vuejs/vitepress/issues/4669) and [#3071](https://github.com/vuejs/vitepress/issues/3071).
- Direct evidence for doing this inside the rework rather than after it: two-thirds of the diff is navbar files that [#5397](https://github.com/vuejs/vitepress/issues/5397) has just rewritten, so this PR cannot rebase cleanly. The feature is one variable's worth of work once the sidebar owns its width; as a standalone PR it is a cross-component patch.
- **[#4739](https://github.com/vuejs/vitepress/issues/4739) — feat(default-theme): collapsible sidebar** (kind: pr-unmerged; demand: 👍0, 2c)
- The earlier attempt at the same feature (+141/−24); the author closed it and returned with a revised UI, which became [#5105](https://github.com/vuejs/vitepress/issues/5105).
- Same rework item; its history shows the blocker was UI design, not feasibility, so the rework should settle the collapsed-state visual up front.
- **[#1054](https://github.com/vuejs/vitepress/issues/1054) — Reduce layout shifts with classic scrollbars** (kind: issue-open; demand: 👍0, 8c)
- Pages with and without a vertical scrollbar render the content column at different widths, so navigating shifts the whole layout on classic-scrollbar platforms.
- Sidebar-width coupling to layout. [#5397](https://github.com/vuejs/vitepress/issues/5397) investigated `scrollbar-gutter: stable` and declined it for [#5310](https://github.com/vuejs/vitepress/issues/5310) (gutter painting), which leaves this sibling symptom unaddressed — the rework should decide the width-coupling story for `VPSidebar`, `VPContent`, `VPLocalNav` and `VPFooter` in one place, as [#5397](https://github.com/vuejs/vitepress/issues/5397) did for the bar.
- **[#1844](https://github.com/vuejs/vitepress/issues/1844) — fix(theme): avoid layout shift caused by scrollbar** (kind: pr-open; demand: 👍5, 2c)
- Fixes [#1054](https://github.com/vuejs/vitepress/issues/1054) by pinning `VPContent`/`VPFooter` to `100vw` above 768px; the author re-verified it on the current release and asked whether v2 is the moment to land it.
- Absorb the intent, not the patch: `100vw` is the same full-bleed hazard [#5397](https://github.com/vuejs/vitepress/issues/5397) removed when it deleted the `-100vw` navbar background bleed. The rework should reach the same result through the shared width variables. Note this is a resubmission of [#1568](https://github.com/vuejs/vitepress/issues/1568), and [#5198](https://github.com/vuejs/vitepress/issues/5198) is a third independent attempt at the same bug — three PRs for one issue is a signal it needs a structural answer.
- **[#4048](https://github.com/vuejs/vitepress/issues/4048) — Add #sidebar-nav-active-link-after slot + expose VPDocAside** (kind: issue-open; demand: 👍0, 3c)
- Wants the page outline nested under the active sidebar item to save horizontal space; the author already achieves a rougher version via `#sidebar-nav-after` and CSS.
- The thread establishes that `aside: 'left'` is not a substitute (it consumes a separate column). The rework's slot surface should include a per-item insertion point, which is cheap while the item component is being rewritten and impossible to retrofit cleanly afterwards.
- **[#4114](https://github.com/vuejs/vitepress/issues/4114) — Sidebar's base need extends parent's base** (kind: issue-open; demand: 👍0, 3c)
- Nested sidebar items should inherit and extend their parent's `base` rather than replacing it.
- Real objection on the thread: inheritance leaves no way to *escape* a parent base, and the docs site itself relies on one group member having a different base. A rework can satisfy both with an explicit opt-out (an absolute link, or a `base: null`), which the current replace-only semantics cannot express. Related: closed-as-duplicate [#4821](https://github.com/vuejs/vitepress/issues/4821).
## Not viable
- **[#1297](https://github.com/vuejs/vitepress/issues/1297) — auto sidebar mode** (kind: issue-open; demand: 👍17, 19c) — Build-time config generation, not a component or CSS concern; a `VPSidebar` rework cannot close it. (Highest-demand sidebar issue; four community plugins named in-thread.)
- **[#1737](https://github.com/vuejs/vitepress/issues/1737) — Auto-sidebar mode** (kind: issue-not-planned; demand: 👍5, 6c) — Declined as duplicate of [#1297](https://github.com/vuejs/vitepress/issues/1297); decline still correct.
- **[#482](https://github.com/vuejs/vitepress/issues/482) — Support setting sidebar depth in themeConfig** (kind: issue-not-planned; demand: 👍5, 2c) — Outline moved out of the sidebar in theme-next; successor setting `themeConfig.outline.level` exists — satisfied, not blocked.
- **[#1493](https://github.com/vuejs/vitepress/issues/1493) — Ability to collapse subcategories in the sidebar** (kind: issue-not-planned; demand: 👍0, 1c) — Duplicate of [#1360](https://github.com/vuejs/vitepress/issues/1360), shipped in [#1835](https://github.com/vuejs/vitepress/issues/1835); nested collapse works today.
- **[#1718](https://github.com/vuejs/vitepress/issues/1718) — expand a collapsible section when navigating to a children page** (kind: issue-not-planned; demand: 👍0, 1c) — Already the default behavior; the named bug was fixed.
- **[#1887](https://github.com/vuejs/vitepress/issues/1887) — [Sidebar] collapsed: true didn't work** (kind: issue-not-planned; demand: 👍0, 2c) — User error, confirmed resolved by reporter.
- **[#4953](https://github.com/vuejs/vitepress/issues/4953) — Show "On this page" sidebar more often in <1280px width** (kind: issue-not-planned; demand: 👍0, 2c) — Declined on measured merit (~80px recoverable); a rework doesn't change the arithmetic. (Note: the local-nav sweep instead treats this as an obligation to make the dropdown an adequate substitute.)
- **[#4266](https://github.com/vuejs/vitepress/issues/4266) — Update VPSidebar.vue** (kind: pr-unmerged; demand: 👍0, 1c) — padding-bottom is an intentional UX choice, overridable in CSS.
- **[#4847](https://github.com/vuejs/vitepress/issues/4847) — Accordions now use native details/summary** (kind: pr-open; demand: 👍0, 5c) — Duplicate of [#3806](https://github.com/vuejs/vitepress/issues/3806) and unusable as-is (+9626/−61 lockfile noise).
- **[#3069](https://github.com/vuejs/vitepress/issues/3069) — feat(client): Add folding function to the navigation bar** (kind: pr-open; demand: 👍2, 0c) — Navbar scope; reintroduces the nested `role="button"` pattern that [#5397](https://github.com/vuejs/vitepress/issues/5397) and `ed2bfb26` removed.
- **[#4630](https://github.com/vuejs/vitepress/issues/4630) — feat(customization): sidenav components / skip title update** (kind: pr-open; demand: 👍0, 5c) — Extension-API scope; maintainer objection stands; needs full Vue compiler at runtime.
- **[#4637](https://github.com/vuejs/vitepress/issues/4637) — Sidenav Components / Skip title update** (kind: issue-open; demand: 👍0, 0c) — Markdown/compile-pipeline concern; sidebar `text` is `v-html`'d by design.
- **[#5198](https://github.com/vuejs/vitepress/issues/5198) — fix(theme-default): stabilize horizontal layout across pages with/without vertical scrollbar** (kind: pr-open; demand: 👍0, 0c) — Third attempt at [#1054](https://github.com/vuejs/vitepress/issues/1054); track under [#1054](https://github.com/vuejs/vitepress/issues/1054).

@ -0,0 +1,103 @@
# Theming, CSS architecture & layout
Part of the [theme chrome audit](./README.md) · snapshot 2026-08-24 · baselined on [#5397](https://github.com/vuejs/vitepress/pull/5397) (`navbar-redesign`)
Sweep: 369 unique records across the four types, deep-dived 40, verified against the `navbar-redesign` checkout.
## Already solved by [#5397](https://github.com/vuejs/vitepress/issues/5397)
- **[#1897](https://github.com/vuejs/vitepress/issues/1897) — Return semi-transparent header background** (kind: issue-not-planned; demand: 👍0, 3c)
- Asks for the frosted/translucent navbar that was removed; declined ("due to avoid any problems we had with transparent background") with a workaround of six `!important` rules against `.content-body`, `.curtain::before` and `.has-sidebar` — exactly the internals [#5397](https://github.com/vuejs/vitepress/issues/5397) deletes. The single `::before` surface plus `--vp-nav-backdrop-filter` / `--vp-nav-bg-color` reduces it to the documented two-line glass recipe. Residual not covered: the thread's second complaint ("scroll fast enough and content is briefly visible below navbar") is the scroll-state flash the planned scroll-driven-animations enhancement targets.
- **[#3383](https://github.com/vuejs/vitepress/issues/3383) — Fix transparent nav bar** (kind: pr-unmerged; demand: 👍0, 5c)
- Config flag for a transparent navbar on non-home pages. Declined by kiaking: "Should be configurable by css variable customization… I think you already can by customizing `--vp-nav-bg-color`." That prescribed route only truly works now. Carries one unbanked idea — kiaking's own "we should make navbar bg transparent by default on `page` when there is no `sidebar` and no `local-nav`", which the new single state rule could express as one extra condition.
- **[#5097](https://github.com/vuejs/vitepress/issues/5097) — Allow overflow-x with horizontal scrolling in VPNav** (kind: pr-unmerged; demand: 👍0, 4c)
- Superseded by the priority-plus `⋯` menu. Also confirms the popover follow-up's value: the author was stuck on "the drop-down menus are no longer floating… I tried simple `z-index` potential fixes but they didn't work" — the scroll-container stacking trap that top-layer rendering removes structurally.
## Fits a planned follow-up
- **[#3806](https://github.com/vuejs/vitepress/issues/3806) — sidebar uses native <details> for collapsible groups** (kind: pr-open; demand: 👍0, 12c)
- Enhancement: `details`/`summary` + `::details-content`, plus the sidebar half of the rework. Adds requirements: (a) it is CONFLICTING and needs a rebase onto the new `VPSidebarItem` (native buttons via ed2bfb26), so the follow-up is now a markup swap plus animation, not an a11y fix; (b) behavior decision from review — brc-dd wants navigating to a page inside a collapsed group to auto-uncollapse it, which native `open` state must be driven to honour; (c) e2e tests in `__tests__/e2e/multi-sidebar/index.test.ts` change; (d) the animation gap that blocked it is what `::details-content` + `transition-behavior: allow-discrete` + `interpolate-size: allow-keywords` now closes.
- **[#4847](https://github.com/vuejs/vitepress/issues/4847) — Accordions now use native details/summary** (kind: pr-open; demand: 👍0, 5c)
- Same enhancement; two competing open PRs (both CONFLICTING, both last touched 2026-08-22) must be reconciled — pick one lineage before the `::details-content` work starts.
- **[#3804](https://github.com/vuejs/vitepress/issues/3804) — sidebar: use native <details> for collapsible groups** (kind: issue-open; demand: 👍0, 1c)
- Acceptance criterion the PR body omits: Space-key toggling, asserted in the e2e disclosure tests.
- **[#3517](https://github.com/vuejs/vitepress/issues/3517) — accessibility: interactive controls should not be nested** (kind: issue-open; demand: 👍0, 1c)
- Requirement: whichever disclosure markup lands must not nest a control inside the link — `<summary>` containing a link reproduces the same nesting, so the group-header-with-link case needs an explicit resolution.
- **[#2056](https://github.com/vuejs/vitepress/issues/2056) — feat(theme): move to css logical properties** (kind: pr-unmerged; demand: 👍0, 2c)
- The prior attempt at the exact planned change, theme-wide. Adds: (a) the decline was not about RTL correctness or double-flip — Evan You rejected it as "a rather significant change for little perceived benefits… contributors are likely more familiar with transitional properties", so the follow-up needs a scope/benefit argument, which the RTL work ([#5034](https://github.com/vuejs/vitepress/issues/5034)/[#5071](https://github.com/vuejs/vitepress/issues/5071)) now supplies; (b) brc-dd's concrete regression from that attempt — "in outline that green border isn't been moved on scroll" — JS that reads/writes physical offsets breaks silently, so the outline marker is a required regression test.
- **[#2794](https://github.com/vuejs/vitepress/issues/2794) — Disable automatically set direction in <html>** (kind: issue-open; demand: 👍0, 3c)
- Enhancement: logical properties. Once the chrome is direction-agnostic, runtime `dir` flipping becomes viable without a rebuild — currently blocked because physical properties plus the `/*rtl:ignore*/` escape hatches bake direction in at build time.
- **[#4359](https://github.com/vuejs/vitepress/issues/4359) — Local navigation dropdown misplaced without sidebar** (kind: issue-open; demand: 👍0, 5c)
- Verified still present on `navbar-redesign`: `VPLocalNavOutlineDropdown.vue:172` applies `left: calc(var(--vp-sidebar-width) + 2rem)` unconditionally at ≥60rem, and `VPLocalNav.vue:90` does the same with `padding-left`. Requirement: the local nav needs the shared-geometry token the navbar just got (`--vp-nav-col-offset`, resolving to `0px` without a sidebar).
- **[#3433](https://github.com/vuejs/vitepress/issues/3433) — Use color-mix instead of multiple color CSS vars** (kind: issue-open; demand: 👍0, 1c)
- Enhancement: `light-dark()` token consolidation. The sole decline reason was the old Vite-era browser floor — invalidated: repo is on `vite ^8.2.1`, `base.css` already ships `@layer __vitepress_base`, `color-mix()` is Baseline widely available. Pair `color-mix()` with `light-dark()` in the same pass — brc-dd's own roadmap on [#4425](https://github.com/vuejs/vitepress/issues/4425) names them together ("p3 colors / color-mix for brand colors?, font-relative units and dir-relative properties, zero specificity selectors").
- **[#4471](https://github.com/vuejs/vitepress/issues/4471) — allow multiple different color modes in addition to light|dark** (kind: issue-open; demand: 👍0, 0c)
- Constraint on `light-dark()`, not a fit: it resolves solely through `color-scheme: light | dark`, so consolidating tokens into it hard-codes the two-mode assumption. Keep a plain-token override seam (or scope `light-dark()` to the chrome only).
## New candidates for future rework
- **[#4125](https://github.com/vuejs/vitepress/issues/4125) — Put all styles in @layer for lowered specificity** (kind: pr-unmerged; demand: 👍0, 2c)
- Declined only on the old browser floor; dead reason — `base.css:1` already opens with `@layer __vitepress_base` (via completed [#4425](https://github.com/vuejs/vitepress/issues/4425)). Remaining work: extend layering from `base.css` to the chrome component styles. [#5397](https://github.com/vuejs/vitepress/issues/5397) makes it safer: stable public class names (aliases kept) mean layering changes precedence without changing selectors.
- **[#2071](https://github.com/vuejs/vitepress/issues/2071) — Add support of Global Notification** (kind: issue-open; demand: 👍0, 5c)
- Architectural finding: `--vp-layout-top-height` is threaded as `var(--vp-layout-top-height, 0px)` through ten files, and users must inject head JS to set it. The single-surface pass is the moment to make the banner participate in flow, or derive the offset from the element instead of a JS-written variable. Also: `--vp-z-index-layout-top: 40` sits above `--vp-z-index-nav: 30`, so a sticky banner paints over the navbar.
- **[#1147](https://github.com/vuejs/vitepress/issues/1147) — Style error when custom max-width** (kind: issue-not-planned; demand: 👍0, 8c)
- Cause named in-thread: "many style don't use css variables, instead use accurate numbers like `max-width: 688px`". The same de-duplication [#5397](https://github.com/vuejs/vitepress/issues/5397) did inside the navbar, applied to `VPDoc`/`VPSidebar`/`VPContent`, would make `--vp-layout-max-width` and `--vp-sidebar-width` actually load-bearing. bluwy's answer on [#4669](https://github.com/vuejs/vitepress/issues/4669) confirms users are told to hand-edit `.VPDoc .aside` and `.content-container`.
- **[#1054](https://github.com/vuejs/vitepress/issues/1054) — Reduce layout shifts with classic scrollbars** (kind: issue-open; demand: 👍0, 8c) *(delta on known [#5310](https://github.com/vuejs/vitepress/issues/5310))*
- Delta the PR body's "`scrollbar-gutter: stable` … not planned" reasoning does not account for: kiaking explicitly reversed on the modal case ("Modal thing should be fixed. Let's fix this issue then"), and zqianem tested the alternatives — "`scrollbar-gutter` shows the gutter above the modal scrim", "`overflow: overlay` shows the scrollbar above the modal scrim and doesn't prevent the underlying content from scrolling". The scroll-lock/modal path needs a different mechanism.
- **[#1844](https://github.com/vuejs/vitepress/issues/1844) — fix(theme): avoid layout shift caused by scrollbar** (kind: pr-open; demand: 👍5, 2c)
- Highest-👍 PR in the area; repeatedly rebased; author asked whether v2 is the moment. [#5397](https://github.com/vuejs/vitepress/issues/5397) changes the calculus: its `100vw` edits existed because the old chrome relied on viewport-width bleed — deleted. Rebased, the PR shrinks to roughly the `body` rule alone, and the `overflow-x: hidden` risk is much reduced.
- **[#5198](https://github.com/vuejs/vitepress/issues/5198) — stabilize horizontal layout (html { overflow-y: scroll })** (kind: pr-open; demand: 👍0, 0c)
- A third, unlinked mechanism for the same problem as [#1054](https://github.com/vuejs/vitepress/issues/1054)/[#1844](https://github.com/vuejs/vitepress/issues/1844)/[#5310](https://github.com/vuejs/vitepress/issues/5310). Three open PRs propose three different fixes for one bug — settle on one during the single-surface pass.
- **[#4884](https://github.com/vuejs/vitepress/issues/4884) — Layout shift when search modal toggle** (kind: issue-open; demand: 👍0, 0c)
- The concrete case kiaking already agreed to fix on [#1054](https://github.com/vuejs/vitepress/issues/1054), still open. Belongs with whichever scroll-lock mechanism the above resolves to.
- **[#2347](https://github.com/vuejs/vitepress/issues/2347) — feat(theme): add appearance transition feature** (kind: pr-unmerged; demand: 58 reactions total: 🚀33 ❤️19 👀6; 11c)
- View Transitions circle-reveal on dark-mode toggle. Strongest community signal in this area. Declined on brand/trend grounds, but with an explicit opening: "it would be super cool if default theme has a cool way for users to hook in this kind of [effect]". Hooks since partly landed (`appearance.onChanged`, `appearance.disableTransition`, open [#4957](https://github.com/vuejs/vitepress/issues/4957)), but brc-dd's caveat — "things might appear wonky if you're using the default theme" — was the six scattered nav background selectors. With one `::before` surface ("every state change below is color-only, so nothing ever moves"), a documented appearance-transition recipe is now tractable without shipping an opinionated animation. Gate on `prefers-reduced-motion`.
- **[#3313](https://github.com/vuejs/vitepress/issues/3313) — Consider not using opacity for any text content** (kind: issue-open; demand: 👍0, 1c)
- Text dimmed via `opacity` shows overlapping glyph strokes in some Persian/Arabic fonts, especially dark mode. Fits the token pass: dedicated `color-mix()`-derived tokens fix the artifact and remove an opacity-induced stacking context. RTL-adjacent correctness item.
- **[#5209](https://github.com/vuejs/vitepress/issues/5209) — New CSS custom properties** (kind: issue-not-planned; demand: 👍0, 0c)
- Maintainer left an explicit opening on the close of its PR [#5211](https://github.com/vuejs/vitepress/issues/5211): "create a separate PR if parts regarding line-height are still relevant". Line-height is the one uncovered gap left in the custom-property surface.
- **[#1764](https://github.com/vuejs/vitepress/issues/1764) — Navbar is not sticky in mobile breakpoint** (kind: issue-not-planned; demand: 👍1, 2c)
- Confirmed by design on the branch: `VPNav.vue` is `position: relative`, `fixed` only at ≥60rem. The decline was made when a fixed mobile bar meant fighting the curtain, the `-100vw` bleed and `overflow: hidden` parents. Those are gone, so revisiting is now cheap.
- **[#3021](https://github.com/vuejs/vitepress/issues/3021) — Make the default theme compatible with 3rd-party CSS frameworks** (kind: issue-open; demand: 👍1, 0c)
- Generic class names (`.menu`) collide with CSS frameworks. Adjacent to [#4125](https://github.com/vuejs/vitepress/issues/4125) and brc-dd's [#4425](https://github.com/vuejs/vitepress/issues/4425) roadmap note ("zero specificity selectors and better internal naming"). [#5397](https://github.com/vuejs/vitepress/issues/5397) constrains the shape: renaming must be additive — new scoped names alongside the kept aliases, ideally inside a cascade layer.
- **[#3194](https://github.com/vuejs/vitepress/issues/3194) — TOC aside height problem** (kind: issue-open; demand: 👍0, 0c)
- The aside is the last piece of chrome using viewport-relative geometry where container-relative would be correct. The PR body rules container queries out for the bar specifically; the aside is the opposite case and the natural place for them during the rework.
## Not viable
- **[#4920](https://github.com/vuejs/vitepress/issues/4920) — CSS Modules in default theme** (issue-not-planned; 👍0, 6c) — Stable user-targetable class names are required; [#5397](https://github.com/vuejs/vitepress/issues/5397) strengthens that constraint.
- **[#3534](https://github.com/vuejs/vitepress/issues/3534) — breadcrumb** (issue-open; 👍7, 11c) — [This sweep's view: blocker is the data model — markdown files don't know ancestors' titles. NB other sweeps dispute; resolved in the [README](./README.md).]
- **[#3160](https://github.com/vuejs/vitepress/issues/3160) — Full width layout for larger screens** (issue-not-planned; 👍0, 2c) — Typographic decline unaffected.
- **[#4953](https://github.com/vuejs/vitepress/issues/4953) — Show "On this page" more often <1280px** (issue-not-planned; 👍0, 2c) — ~80px recoverable; unaffected.
- **[#5178](https://github.com/vuejs/vitepress/issues/5178) — Cookie consent banner** (issue-not-planned; 👍0, 1c) — Out of scope; layout side covered under [#2071](https://github.com/vuejs/vitepress/issues/2071).
- **[#2938](https://github.com/vuejs/vitepress/issues/2938) — grayscale filter** (issue-not-planned; 👍0, 2c) — One-line custom.css override.
- **[#4917](https://github.com/vuejs/vitepress/issues/4917) — nav icon repaint claims** (issue-not-planned; 👍0, 4c) — Premise disputed with measurements.
- **[#4439](https://github.com/vuejs/vitepress/issues/4439) — --vp-c-text-1 bundle output** (issue-not-planned; 👍0, 1c) — Fixed in later releases.
- **[#703](https://github.com/vuejs/vitepress/issues/703) — font size follows browser settings** (pr-unmerged; 👍0, 1c) — Superseded by the rem migration.
- **[#5211](https://github.com/vuejs/vitepress/issues/5211) — css custom properties for font sizes** (pr-unmerged; 👍0, 1c) — Closed as unnecessary post-rem; line-height remainder tracked as [#5209](https://github.com/vuejs/vitepress/issues/5209).
- **[#4413](https://github.com/vuejs/vitepress/issues/4413) — theme switcher tri-state** (issue-not-planned; 👍1, 3c) and **[#5159](https://github.com/vuejs/vitepress/issues/5159) — auto detect system theme** (pr-unmerged; 👍0, 0c) — Product decline ("keep the UI simple as a toggle") undisturbed.
- **[#2912](https://github.com/vuejs/vitepress/issues/2912) — Hide JS-required features if JS disabled** (pr-open; 👍0, 9c) and **[#2680](https://github.com/vuejs/vitepress/issues/2680)** (issue-not-planned; 👍0, 4c) — kiaking: "wouldn't it make more sense if we create a new JS free theme"; structural objection stands.
- **[#2133](https://github.com/vuejs/vitepress/issues/2133) — smooth scroll** + duplicate cluster [#978](https://github.com/vuejs/vitepress/issues/978)/[#979](https://github.com/vuejs/vitepress/issues/979)/[#981](https://github.com/vuejs/vitepress/issues/981)/[#1002](https://github.com/vuejs/vitepress/issues/1002)/[#1449](https://github.com/vuejs/vitepress/issues/1449)/[#1544](https://github.com/vuejs/vitepress/issues/1544) — UI declined; motion-safety already handled (`base.css:232` forces `scroll-behavior: auto !important` under reduced motion).
- **[#4215](https://github.com/vuejs/vitepress/issues/4215) — page width changes when scrollbar appears** (issue-not-planned; 👍0, 3c) — Duplicate of [#1054](https://github.com/vuejs/vitepress/issues/1054).
Loading…
Cancel
Save