27 KiB
Theme chrome audit — after the navbar redesign
Snapshot 2026-08-24 · baselined on #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. ≈120 unique records assessed, 60+ deep-dived. Every "solved" and "still broken" claim was verified against the
navbar-redesigncheckout, not taken from the threads. Spot-check before acting on close recommendations.
Files
- follow-ups.md — the plan of record, moved from the #5397 description
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 — the bar, mobile screen, dropdowns, overflow
- local-nav.md — VPLocalNav, outline dropdown, return-to-top
- sidebar.md — drawer, groups, scroll, data layer
- theming-css.md — variables, layout shift, modern CSS
- a11y-i18n.md — focus, ARIA, labels, direction
- pr-1273.md — what #1273 contained vs what was adopted
Fix now — live defects verified on the branch
Small, self-contained, and each lands on code #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, #1325, #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 |
| 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 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 |
| 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 |
| 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 |
The local nav + sidebar rework — requirements register
The follow-up #5397's body already names, now with concrete obligations. Largest bucket: 40+ records converge here. Full detail in local-nav.md and sidebar.md.
Geometry and presence
- #4359 / #4393 — the outline dropdown hard-codes
left: calc(var(--vp-sidebar-width) + 2rem)unconditionally; the local nav does the same withpadding-left. Give the local nav the presence-derived column offset the navbar just got (--vp-nav-col-offset→0pxwithout a sidebar). Absorb the outcome of #4393 (LGTM'd, 16 comments, stalled) — not its per-component override — and credit its author. - #3811 — the nav-height probe is wrong twice: it appends to
document.bodywhere the.hide-navoverride can't reach it (sonavbar: falsealways resolves 4rem), andvp-doc.cssbakes the local nav's height as a magic2.9375reminto every heading'sscroll-margin-top(#2334). Replace both with a declared--vp-local-nav-heighttoken; gate rendering on layout type, not raw scroll. - #2071 —
--vp-layout-top-heightthreads 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: 40paints above the nav's 30. - #1764 — "only the secondary nav is sticky" leaves mobile home with nothing sticky at all. The obstacles the decline anticipated (curtain,
-100vwbleed) are gone; state which element owns stickiness per layout.
The outline dropdown, made first-class
- #5090 / #5091 —
useActiveAnchoris single-consumer, which is why #5091 forked a parallel composable. Generalize it to two consumers; the fork and the dropdown'sdata-allow-mismatch="style"band-aid both disappear. The same generalization enables #2146 (auto-expand the active outline branch, 👍8) in both surfaces. - #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 — the decline's load-bearing claim was that the dropdown suffices below 1280px. That makes the three items above obligations, not enhancements.
- #4521 / #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
scrollToTopandreturnToTopLabel. - #2297 —
hasAside(config-only) andhasLocalNav(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 + #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 is marked completed but reported regressed — re-verify. - #4330 — the swipe decline stands, but the unanswered half doesn't:
useSidebarControl's open/close isn't exported, so custom layouts click.VPBackdropprogrammatically. Expose it. - #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 vs #4847 — two competing native-
<details>PRs, both CONFLICTING aftered2bfb26; pick a lineage before the::details-contentwork. 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 nativeopen, the summary-link nesting must not recreate #3517, and Space-key toggling belongs in the e2e disclosure tests (#3804). - #4211 + #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 + #563 — the 5-level depth cap exists only because
textTagcomputesh{depth+2}and heading levels run out ath6; #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 — items key on
i.text(collides) whileVPSidebarremounts 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 — 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, #4296, #3426, #2881 and PRs #3654 (👍6), #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 —
activeMatchfor sidebar items (👍9, highest-demand actionable sidebar issue). Constraint from #5395: ancestor marking rideshas-activeand must not widenaria-currentbeyond the exact match.
Data layer
- #4841 / #4842 — multi-sidebar keys prefix-match without trailing-slash normalization (
/api-examples.mdmatches the/asidebar). The public type documents keys as directories; make matching agree. Verify against the docs site's own config. - #4114 — nested
baseshould extend the parent's, with an explicit escape hatch (absolute link orbase: null) that answers the thread's real objection.
Features that should ride the rework, not follow it
- #5105 (desktop sidebar collapse; issue #3071, prior attempt #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 / #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 (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-currenton the leaf only. - #4048 — a per-item slot insertion point is cheap while the item component is being rewritten, impossible to retrofit cleanly after.
- #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 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-modemust 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.
Contracts the #4000 follow-up must widen
- #2866 + #2831 — slot content is invisible to the overflow engine in both axes: width (re-introduces the crowding #2842 fixed) and height (the fixed-height bar lets over-tall slot content paint over the page — the #1273 delta). The #4000 contract should cover slot content and decide once: measure-and-collapse, grow, or document fixed-height as a constraint.
- #3069 — nested groups render but aren't disclosures:
VPMenuGroupstill emits a static<p class="title">. The nested-dropdowns follow-up (#3816) needs per-groupcollapsed?: booleanwith the disclosure treatment the screen variant already has. Absorb the feature, not the patch — it reintroduces nestedrole="button". - #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 — nav links ignore
rewrites; every nav href now flows throughuseNavItemLink, so normalization has exactly one insertion point. - #4347 — the locale switcher links to 404s; all three renderings share one
useLangscall, so an existence check with ancestor fallback fixes every surface. Cross-link #3312/#3275 (content-side of the same gap). - #3383 — kiaking's counter-proposal (transparent by default on
layout: pagewith 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 — 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 + #2913 + #3123 — the theme has zero
@media printrules. 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 — per-page navbar hiding: kiaking's last word was "I think we can add this feature… open a new issue" — nobody did.
--vp-nav-heightis now the single geometry input, so zeroing it per-page is one rule (and the.hide-navoverride must live where all consumers can see it). - #4141 — inline SVG logo: the one bar element CSS can't theme. Supersedes not-planned #1742.
- #2706 — a slot below the bar for full-bleed banners; composes with the surface and
--vp-nav-heightconsumers, natural home inVPNav.vue. - #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.
- #4125 — extend
@layerfrombase.css(already layered via #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 — any renaming must be additive alongside the aliases. - #1147 — make
--vp-layout-max-widthand--vp-sidebar-widthload-bearing inVPDoc/VPSidebar/VPContent; the named cause (hardcodedmax-width: 688px-style numbers) is the same duplication class #5397 deleted inside the navbar. - #3433 —
color-mix()withlight-dark()in one pass (brc-dd's own #4425 roadmap pairs them; the decline's browser floor is dead). Constraint from #4471:light-dark()hard-codes the two-scheme model — keep a plain-token override seam. Fold in #3313: replaceopacity-dimmed text with mixed tokens (fixes Persian/Arabic glyph-overlap artifacts, removes stray stacking contexts). - #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: the unconditional
dirwriter inapp/index.tscan become an opt-out or write-on-change guard, enabling runtime direction toggles. - #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.
- Scrollbar layout shift — #1054 (8c) · PRs #1844 (👍5), #5198 · #4884 · delta on #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-gutterwas tested to fail exactly there (gutter paints above the modal scrim). #5397 shrinks #1844 to roughly one body rule by deleting the100vwbleed it compensated for. - Appearance-transition recipe — #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 onprefers-reduced-motion) satisfies the demand without shipping an opinionated animation. - Route announcer — #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 toVPSkipLinkis the ready-made mount point for a polite live region.
PR #1273 — adopted vs left behind
Full comparison in 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 | Nested sidebar controls — fixed on main by ed2bfb26 (native button toggles); the axe complaint is resolved. |
| #5251 | Same fix landed the other way round in ed2bfb26; obsolete. |
| #3351 | Aside marker visibility — resolved by merged #5377, never closed. |
| #2259 | Fully satisfied: switch titles shipped in #3311, name/state split completed by #5397. |
| #1332 | The opposite — marked completed but reported regressed (#2329); re-verify, #5397's inert work likely re-fixes it. |
| on #5397 merge | Keywords close #2842, #5364, #5376; superseded unmerged PRs #5097, #1283, #1448, #2329, #5074, #4978, #2260, #2741, #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 (👍17) | Auto-sidebar from the filesystem — highest demand in the audit, but build-time config generation; no component rework can close it. |
| #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 | CSS Modules — stable targetable class names are a contract #5397 strengthens. Layering (#4125) is the compatible alternative. |
| #4413 · #5221 · #1008 · #3160 | Product-design declines (tri-state switch, arrow-key paging, secondary navbars, full-width layout) untouched by the architecture. |
| #4917 · #4938 · #2747 | Premise disputed with measurements · heading semantics correct with documented opt-out · working as designed. |
| Outline PR cluster | #4735, #3387, #4634, #4457, #2189, #2676 — all superseded by merged #5377 or the rewritten outline.ts. |
Demand signals
| Record | Signal | Where it lands |
|---|---|---|
| #2347 | 58 reactions · 11c | Appearance-transition recipe (decision) |
| #1297 | 👍17 · 19c | Not viable (build-time; plugins) |
| #2257 | 👍9 · 7c | Rework — sidebar activeMatch |
| #2146 | 👍8 | Rework — outline auto-expand |
| #3534 | 👍7 · 11c | Rework — breadcrumbs from resolved sidebar |
| #1037 + #4532 | 👍7 + 👍6 | Rework — footer with sidebar |
| #3654 cluster | 👍6 + 👍3 + 👍2 | Rework — auto-reveal active item (#5194 spec) |
| #1844 | 👍5 | Scrollbar-shift decision |
| #3806 + #4847 | 12c + 5c | Rework — native <details> groups |
| #2913 | 7c | Print styles |