Redesign documentation navigation to horizontal top nav bar#391
Redesign documentation navigation to horizontal top nav bar#391WGB5445 wants to merge 2 commits into
Conversation
…av bar Redesigned the documentation navigation to follow the pattern used by Claude Code Docs and other modern documentation sites: Before: Top-level categories (Guides, SDKs, Smart Contracts, etc.) were rendered as vertical tabs inside the left sidebar. After: Top-level categories are now a horizontal navigation bar below the main header, with the left sidebar showing only the sub-navigation for the currently active section. Changes: - New TopNav.astro component: horizontal nav bar with active state indicator (underline accent), extracted from sidebar group data - Header.astro: integrated TopNav below the main header row - Sidebar.astro: removed TabbedContent tab switcher, now shows only the active section's entries with a section title header - PageFrame.astro: adjusted sidebar content padding for cleaner layout Responsive behavior: - Desktop (>=50rem): TopNav shown in header, hidden from sidebar - Mobile (<50rem): TopNav hidden from header, shown inside the sidebar drawer for mobile menu access
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
The TopNav was being squeezed into the same row as the logo/search because PageFrame's header had a fixed height constraint. Fix: - Added --sl-top-nav-height and --sl-header-total-height CSS variables - PageFrame header now uses flex-direction: column on desktop to stack the header content and TopNav vertically - Sidebar and main-frame positioning use --sl-header-total-height on desktop so they offset correctly below both header rows - Header.astro inner content takes --sl-nav-height height, TopNav fills the remaining --sl-top-nav-height space
There was a problem hiding this comment.
Pull request overview
Redesigns the documentation navigation UI to use a horizontal top navigation bar for top-level sections, while the left sidebar shows only the active section’s sub-navigation (with responsive behavior for mobile vs desktop).
Changes:
- Added a new
TopNav.astrocomponent to render top-level documentation sections horizontally. - Updated Starlight overrides (
Header.astro,Sidebar.astro,PageFrame.astro) to integrate the top nav and simplify the sidebar to only the active section. - Introduced a desktop-only CSS variable for combined header height to support correct sticky sidebar/layout offsets.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| src/styles/global.css | Adds desktop-only CSS variables for top-nav/header combined height. |
| src/starlight-overrides/Sidebar.astro | Replaces tabbed sidebar UI with active-section-only sidebar and mobile TopNav placement. |
| src/starlight-overrides/PageFrame.astro | Adjusts header/sidebar sizing and sticky offsets to account for the new top nav row. |
| src/starlight-overrides/Header.astro | Renders TopNav as a second row in the header on desktop viewports. |
| src/components/TopNav.astro | New component to render top-level groups as a horizontal navigation bar. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| return { | ||
| label: group.label, | ||
| icon: sidebarGroupIcons[key] || "ph:rocket-launch", | ||
| href: findFirstLink(group.entries) || "#", | ||
| isActive: hasCurrentPage(group.entries), |
There was a problem hiding this comment.
The returned topGroups objects include an icon field, but the icon is never rendered in the markup. This makes the sidebarGroupIcons/label-to-key mapping (and the icon computation) unnecessary work/coupling. Either render the icon in the nav items or remove the icon/key/labels mapping until it’s needed.
| topGroups.map(({ label, href, isActive }) => ( | ||
| <a class:list={["top-nav-item", { active: isActive }]} href={href}> | ||
| {label} | ||
| </a> |
There was a problem hiding this comment.
Expose the active state to assistive tech by setting aria-current on the active top-level link when isActive is true. This helps screen readers announce the current documentation section.
| import { stripLeadingAndTrailingSlashes } from "node_modules/@astrojs/starlight/utils/path"; | ||
| import { sidebarGroupIcons } from "../../astro.sidebar.ts"; | ||
| import labels from "../../src/content/nav/en"; | ||
| import TabbedContent from "~/components/tabs/TabbedContent.astro"; | ||
| import TabListItem from "~/components/tabs/TabListItem.astro"; | ||
| import TabPanel from "~/components/tabs/TabPanel.astro"; | ||
| import { stripLangFromSlug } from "~/utils/path-utils"; |
There was a problem hiding this comment.
stripLeadingAndTrailingSlashes is being imported via the bare specifier node_modules/@astrojs/starlight/utils/path, which is not a stable/resolvable module path in standard Vite/Astro resolution (it will try to resolve a package named node_modules). Import from Starlight’s public entrypoint (e.g. @astrojs/starlight/utils/path) or inline a small local helper to avoid a build-time module resolution failure.
| const activeGroup = sidebarGroups.find( | ||
| (group) => group.type === "group" && group.entries && hasCurrentPage(group.entries), | ||
| ); | ||
| const activeEntries = activeGroup?.entries ?? sidebarGroups[0]?.entries ?? []; | ||
| const activeSectionLabel = activeGroup?.label ?? ""; |
There was a problem hiding this comment.
activeEntries falls back to the first group’s entries when no group matches the current page, but activeSectionLabel does not fall back similarly. This can render a blank section title while still showing entries (e.g., if isCurrent isn’t set for a route). Consider falling back to sidebarGroups[0]?.label when activeGroup is undefined so the title always matches the displayed entries.
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| const sidebarGroups = (Array.isArray(sidebarData) ? sidebarData : []) as any as SidebarEntryType[]; |
There was a problem hiding this comment.
There are two consecutive eslint-disable-next-line @typescript-eslint/no-explicit-any comments above the same cast. One of them is redundant; please remove the extra disable to keep the lint annotations minimal.
| function hasCurrentPage(entries: SidebarEntry[]): boolean { | ||
| return entries.some((entry) => { | ||
| if (entry.type === "link") return !!entry.isCurrent; | ||
| if (entry.type === "group" && entry.entries) return hasCurrentPage(entry.entries); | ||
| return false; |
There was a problem hiding this comment.
TopNav determines the active section via entry.isCurrent, but this component doesn’t apply the custom “mark current entries” logic that Sidebar.astro applies (including the Move Reference subpath handling / isSubPage behavior). Because the header renders before the sidebar in PageFrame, desktop TopNav can compute isActive before Sidebar mutates Astro.locals.starlightRoute.sidebar, leading to missing/incorrect active highlighting. Consider moving the current-marking logic into a shared utility and invoking it from TopNav too (or deriving active group from route info without relying on sidebar mutation).
Redesign: Move Top-Level Navigation to Horizontal Top Nav Bar
Redesign the documentation navigation to follow the pattern used by Claude Code Docs and other modern documentation sites (e.g. Stripe, Vercel).
Problem
The current sidebar uses vertical tabs for top-level categories (Guides, SDKs & Tools, Smart Contracts, etc.), which takes up significant vertical space and requires users to click a tab before seeing sub-navigation. This pattern is less discoverable and harder to scan than a horizontal top navigation bar.
Solution
Move top-level categories to a horizontal navigation bar positioned below the main header, and simplify the left sidebar to show only the sub-navigation for the currently active section.
Files Changed
src/components/TopNav.astrosrc/starlight-overrides/Header.astrosrc/starlight-overrides/Sidebar.astroTabbedContenttab switcher (−193 lines), now renders only the active section's entries with a section titlesrc/starlight-overrides/PageFrame.astroflex-direction: columnfor two-row layout; sidebar/content offset via--sl-header-total-heightsrc/styles/global.css--sl-top-nav-heightand--sl-header-total-heightCSS custom propertiesResponsive Behavior
Verification
pnpm check— 0 errors, 0 warnings, 0 hints (192 files)pnpm lint— biome + prettier both passpnpm build— Full production build succeeds (1150 HTML pages)pnpm test— 14/14 tests pass✓ All internal links are valid(built-in starlight-links-validator)What This Does NOT Change
astro.sidebar.tsis not modified; sidebar data structure is the same