Navigation: make the top nav configurable in navigation.yml - #3792
Navigation: make the top nav configurable in navigation.yml#3792theletterf wants to merge 4 commits into
Conversation
The bar under the global Elastic header was a hardcoded list of three links in _SecondaryNav.cshtml, so changing it meant changing a template. Add a `top_nav:` key to navigation.yml. Each entry is a link, an external link, or a dropdown with grouped children. Links target either a site relative `url:` or a cross-repo `page:` resolved at assemble time through the existing CrossLinkResolver. The resolved model is stamped onto every set's BuildContext once per assemble and reaches the layout from there. Only assembler builds set it; isolated and codex builds leave it null and keep rendering the built-in links, as does an assemble whose navigation.yml has no `top_nav:`. The shipped default reproduces the current three links, so the rendered header does not change on merge. Dropdowns are native <details>, which does not close on outside click or Escape; secondary-nav.ts adds that with delegated listeners so the behaviour survives htmx body swaps. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The bar put a static "Docs" link on the left and pushed the items to the right edge. Align the items to the left instead and remove the link, so the bar reads as one row of navigation rather than a brand plus a menu. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Guides points at the docs home and APIs at the API reference, matching what nav-v2 declared for the same two sections. Guides uses `/`, which prefix-matches every page, so it is the highlighted entry wherever no more specific entry claims the page. That is the intended catch-all behaviour rather than an accident of prefix matching, so cover it with a test. Also guard the shipped navigation.yml itself: its top_nav drives every assembled page, so a typo there breaks the site rather than one doc. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
@florent-leborgne — the dropdown from #3223 is fully supported here, it just moved to a slightly different schema. Here is how to turn Drop this into - title: Products
children:
- title: Stack products # a child WITH children => group heading
children:
- title: Elasticsearch
page: docs-content://products/elasticsearch/v9.md
- title: Kibana
page: docs-content://products/kibana/v9.mdThat renders exactly the panel in your screenshot: a "Stack products" heading with the two links under it. What changed from your prototype
The dropdown label is a pure toggle, not a link. Your version put an So if - title: Products
children:
- title: All products # childless entries render as links with no heading
url: /products/
- title: Stack products
children:
- title: Elasticsearch
page: docs-content://products/elasticsearch/v9.mdChildless children are collected into an unlabelled run at the position you put them, so this puts "All products" above the "Stack products" heading. If a dropdown landing page turns out to be a real requirement, say so and I will look at making the label both toggle and link properly. Things that will fail the build
Not portedThe Two behaviours you get for free
|

Preview: https://docs-v3-preview.elastic.dev/elastic/docs-builder/docs/3792 (assembled build — this is where the top nav renders; isolated previews do not show this bar)
Why
The bar under the global Elastic header is a hardcoded list of three links in
_SecondaryNav.cshtml, so changing it means changing a template.This extracts the configurable top nav prototyped in #3223 / the
nav-v2branch, rebuilt on the current navigation system. None of the nav-v2 types (NavigationV2File,SiteNavigationV2,SectionNavigationNode,config/navigation-v2.yml) are involved; only the markup shape, the CSS and the YAML shape were carried over.What
A
top_nav:key inconfig/navigation.yml. Each entry is a plain link, an external link (rendered with a ↗ icon andtarget="_blank"), or a dropdown:Links target either a site-relative
url:or a cross-repopage:.page:refs are resolved once per assemble through the existingCrossLinkResolver+PublishEnvironmentUriResolver, so they survive page moves the same way body cross-links do. An unresolvablepage:is a build error.TopNavResolverproduces an immutableTopNavRenderModelwith final hrefs, which is stamped onto every set'sBuildContextand flows to the layout from there.The active item is a server-side longest-prefix match on whole path segments, so
/reference/does not claim/references/xand a dropdown child highlights its parent label. This is safe becausehx-boostswaps the whole<body>and#secondary-navis nothx-preserved, so the header re-renders on every navigation.What changes visually
The bar is now left aligned and the static "Docs" link is gone, so it reads as one row of navigation rather than a brand plus a right-aligned menu. That applies to both the configured and the fallback rendering.
The links themselves do not change: the shipped default
top_nav:reproduces today's Release notes / Troubleshoot / Reference, and iftop_nav:is absent the template renders those same three links from the built-in fallback. Only assembler builds populate the model — isolated and Codex builds do not render this bar at all. The#htmx-indicatorcontract with_GlobalLayout.cshtmlis preserved in both branches.Notes
<details>/<summary>. That does not close on outside click or Escape, which the nav-v2 version left as a defect;secondary-nav.tsadds both with delegated listeners so the behaviour survives htmx body swaps.overflow-x-autoso it scrolls sideways rather than overflowing once several items are configured;secondary-nav-dropdown.cssuses:has()to let an open menu escape that clipping.Testing
SiteNavigationFileTests— YAML parsing through the real static deserializer (links, external,page:, dropdown groups, absent key, invalid URI).TopNavResolverTests— path-prefix application, external detection,page:resolution through a link index, unresolved/conflicting/nesting errors, active-URL matching.SecondaryNavRenderingTests— renders the actual slice: fallback markup, configured links, external attributes, dropdown structure, active state, left alignment with no brand link,#htmx-indicatorin both branches.secondary-nav.test.ts— outside click, sibling close, clicks inside the panel, Escape + focus restore.2,871 C# tests and 152 JS tests pass;
dotnet formatand prettier are clean.Rendering was additionally checked in a browser against the real built
styles.cssandmain.jsbundle: the panel matches the target design, the:has()rule flips the container tooverflow: visibleonly while open, the menu escapes the container, and outside-click/Escape close as expected.Verified on the assembled preview. The preview above renders the bar from
top_nav:inconfig/navigation.yml(the single-line anchor markup is the configured branch, not the built-in fallback), with the environment path prefix correctly applied to each href (/elastic/docs-builder/docs/3792/release-notes/). That exercises YAML parsing,TopNavResolverand theBuildContextthreading in a real assemble. All 24 CI checks pass.🤖 Generated with Claude Code