Navigation

This content was generated with the assistance of AI and should be verified against the official documentation of each tool before being relied on in production.

This section’s bibliography lists the reference material consulted while preparing these pages.

This page was written against Zensical 0.0.72 (the latest release on PyPI, published 2026-10-23, still marked alpha) and checked on 2026-10-26. Navigation in Zensical is controlled by two things: the nav setting, which says which pages form the menu and in which order, and a set of theme feature flags, which say how the menu is drawn and how pages are loaded. The examples use the Pocket Units documentation and were built with Zensical 0.0.72.

What navigation is made of

Without any configuration, the left sidebar is generated from the folder structure and the content of the Markdown files under docs_dir. That is enough for small sites. As soon as order, titles or grouping matter, an explicit nav list is declared. Features then change how the result looks:

  • the top-level entries can become tabs below the header;

  • the top-level sections can become groups in the sidebar;

  • a breadcrumb path can be shown above the page title;

  • the HTML of the sidebar can be pruned so each page carries only the visible items;

  • a section can have its own landing page;

  • the table of contents on the right can follow the reader or move into the left sidebar;

  • pages can be loaded without a full reload, and links can show a preview of their target.

Page layout with header, tabs, sidebar, breadcrumbs, content and table of contents, and the feature flags that control each

The figure shows which region of the page each setting controls.

Explicit navigation

nav is a list under [project] in zensical.toml (or under nav: in mkdocs.yml). Paths are relative to docs_dir. A plain string is a page whose title comes from the page itself. A table with one key gives the entry a custom title. A key whose value is a list is a section. A string that does not resolve to a Markdown page is treated as a URL, which gives an external link in the menu.

zensical.toml
[project]
site_name = "Pocket Units"
site_url = "https://example.org/pocket-units/"
nav = [
  { "Home" = "index.md" },
  { "Guides" = [
    "guides/index.md",
    "guides/getting-started.md",
    "guides/convert-lengths.md",
  ] },
  { "Reference" = [
    "reference/api.md",
    "reference/units.md",
  ] },
  { "Changelog" = "changelog.md" },
  { "Source code" = "https://example.org/pocket-units/source" },
]

With this configuration the build produced five top-level entries, in this order: Home, Guides, Reference, Changelog and Source code. The last one is a link to the external URL.

A page can be kept out of the menu without being removed from the site. Pages listed in not_in_nav are still built, are still searchable and still appear in the sitemap, but they are not part of the default navigation. Pages matched by draft_docs are left out of the navigation too, including in the preview server. In the 0.0.72 release not_in_nav is a multiline string with one pattern per line, not a TOML array: an array makes the build stop with the message Error: 'not_in_nav' must be a multiline string.

zensical.toml
[project]
not_in_nav = """
drafts.md
"""

In the example build, drafts.md was written to site/drafts/index.html and did not appear in the menu.

Enabling features

Navigation features are names in the features list under [project.theme]. The sections that follow explain each flag. This is the list used by the Pocket Units example.

zensical.toml
[project.theme]
features = [
  "navigation.instant",
  "navigation.instant.prefetch",
  "navigation.instant.progress",
  "navigation.tracking",
  "navigation.tabs",
  "navigation.tabs.sticky",
  "navigation.sections",
  "navigation.path",
  "navigation.prune",
  "navigation.indexes",
  "navigation.top",
  "toc.follow",
]

The same list under theme: and features: works in mkdocs.yml. The build of the full example reported No issues found.

Instant navigation

With navigation.instant, the theme intercepts clicks on internal links and loads the next page with an XHR request instead of a full page load, so the site behaves like a single-page application. The search index survives from one page to the next. The feature relies on sitemap.xml, so site_url must be set; without it, instant navigation and instant previews do not work.

Two companions refine it:

  • navigation.instant.prefetch fetches a page when the pointer hovers over a link. The documentation labels it experimental.

  • navigation.instant.progress shows a progress bar at the top of the page, but only when loading takes longer than 400 ms.

navigation.tracking is a related flag: it keeps the address bar in sync with the active anchor of the table of contents, so the current URL always points at the section the reader is looking at.

Because pages are swapped without a reload, JavaScript added through extra_javascript must not assume it runs once per page. The customization page of the official documentation describes the document$ observable for that case; see Theming and Customization.

Instant previews

Instant previews show a small card with the target section when the reader hovers over a link. They are experimental, need site_url, and work only for links to headings on other pages (not for other elements that have an id).

The recommended way to enable them is the zensical.extensions.preview Markdown extension, which lists the pages whose links get a preview. targets selects the pages that previews link to, which is what the documentation recommends; sources selects the pages that show previews and applies to all pages when it is omitted. Exclusion wins over inclusion.

zensical.toml
[[project.markdown_extensions.zensical.extensions.preview.configurations]]
targets.include = ["guides/*"]

A single link can also opt in with the data-preview attribute, written with the Attribute Lists syntax of Markdown.

docs/reference/units.md
See the [Metres to feet](../guides/convert-lengths.md#metres-to-feet){ data-preview } section.

In the built page the anchor was rendered as <a data-preview="" href="../../guides/convert-lengths/#metres-to-feet">. The card itself is drawn by the browser script and was not checked visually here.

Tabs

navigation.tabs turns the top-level entries of nav into a row of tabs below the header. In the example build the tab row contained Home, Guides, Reference, Changelog and Source code. navigation.tabs.sticky keeps the row fixed below the header while the page scrolls and requires navigation.tabs. Tabs are drawn only on viewports wider than 1220 px; narrower screens keep the single menu.

Sections

navigation.sections renders the top-level sections as always-open groups in the sidebar rather than collapsible items. This too applies only above 1220 px. When it is combined with navigation.tabs, the tabs take the top level, so the groups are made from the level-2 items.

navigation.expand goes the other way: it expands every collapsible subsection by default.

Breadcrumbs

navigation.path adds a breadcrumb trail above the page title, which is useful on small screens where the sidebar is collapsed. For guides/getting-started.md the generated list was Home and Guides, followed by the page. A page can hide its own trail through front matter:

docs/guides/convert-lengths.md
---
hide:
  - path
---

# Convert lengths

This front matter was not executed here; the build of the example used hide: [toc] on the same page instead.

Pruning

By default, every page repeats the HTML of the entire navigation tree. navigation.prune includes only the items that are visible for that page, such as the open sections, so pages are smaller. The documentation states a reduction of 33 percent or more in total site size for large sites. In the tiny example the page guides/getting-started went from 15909 bytes to 14916 bytes. The flag is not compatible with navigation.expand, because expanded sections would have to be present in full. The 0.0.72 build accepted both flags together without reporting an error, so the conflict is a rule to follow, not one the tool enforces.

Section index pages

With navigation.indexes, an index.md (or README.md) placed first in a section becomes the landing page of that section: the section title in the sidebar links to it instead of only expanding. In the example, guides/index.md is the first entry of the Guides section and serves as its landing page. The flag is not compatible with toc.integrate; a combined build also raised no error in 0.0.72.

docs/guides/index.md
# Guides

Task-oriented guides for Pocket Units.

Table of contents

The table of contents lists the headings of the current page on the right. Three settings change it:

  • toc.follow scrolls the sidebar so the active heading stays visible in a long page;

  • toc.integrate moves the table of contents into the left sidebar;

  • hide in the front matter removes it for one page, or removes the sidebar too.

docs/guides/convert-lengths.md
---
hide:
  - navigation
  - toc
---

# Convert lengths

navigation.top adds a back-to-top button that appears when the reader scrolls up after scrolling down. Additional navigation, such as previous and next links, is configured in the footer; the official documentation points to its Footer page for that.

Wider content

The content area is limited to about 80 to 100 characters per line. A stylesheet registered with extra_css can widen it.

docs/stylesheets/extra.css
.md-grid {
  max-width: 1440px;
}

Use max-width: initial; to stretch the content to the full screen. This stylesheet was not built in the example.

Pitfalls and version notes

Situation Behaviour

navigation.instant or previews without site_url

They depend on sitemap.xml and do not work.

navigation.expand with navigation.prune

Documented as incompatible; no build error in 0.0.72.

navigation.indexes with toc.integrate

Documented as incompatible; no build error in 0.0.72.

not_in_nav as a TOML array

Build stops: the value must be a multiline string.

Tabs and sections on narrow screens

Not drawn; they apply above 1220 px.

navigation.instant.prefetch and previews

Experimental and may change while Zensical is in the 0.0.x series.