Navigation and Menus
OINK combines Hugo’s content tree and menu model with a documentation workspace: a global navbar, a collapsible and resizable section sidebar, and a collapsible page outline. The same structure works for English, Chinese, and right-to-left languages.
Site navbar
The global navbar is built from Hugo’s main menu plus OINK-generated controls.
Depending on configuration and page type, it can include version, language,
color-mode, and search controls.
Adding main menu entries
Define a menu entry in page front matter:
---
title: Documentation
linkTitle: Docs
menu:
main:
weight: 20
pre: <i class="fa-solid fa-book" aria-hidden="true"></i>
---
Lower weights appear first. A site-level external link is similar:
menus:
main:
- name: GitHub
identifier: github
weight: 50
url: https://github.com/pgsty/oink
pre: <i class="fa-brands fa-github" aria-hidden="true"></i>
Use an identifier for configuration that refers to a menu item. Localize
name or linkTitle in language configuration, but keep identifiers stable.
Version menu
The selector appears when params.versions is configured. Each entry can be a
heading, separator, release, development build, or site variant:
params:
version: v1.0.0
version_menu: v1.0.0
version_menu_pagelinks: true
versions:
- version: v1.1.0-dev
kind: next
url: https://next.example.org/
- version: v1.0.0
kind: latest
url: https://docs.example.org/
version identifies the published site variant and is not necessarily a Git
ref. Commands that require a resolvable tag should use the project’s explicit
release-ref parameter instead. With page links enabled, OINK first tries the
equivalent path on the target version and otherwise uses its configured URL.
Language menu
OINK builds language targets from Hugo’s AllTranslations. When a translated
peer is missing, the target language’s home page is used instead of a broken
URL. One configured language hides the control. With two or more languages, a
click advances to the next language by weight, while hovering for half a second
or focusing the control opens the complete menu. The current site cycles from
English to Simplified Chinese and back. Targets include lang, hreflang,
locale, and text-direction attributes.
Light/dark theme menu
When color-mode support is enabled, the navbar and documentation workspace show a theme control. See Light/dark-mode menu.
Search box
The documentation workspace uses a local search dialog when offline search is enabled. The sidebar button advertises the platform shortcut (Command/Ctrl+K). Online search integrations remain available by explicit configuration. See Search.
Adding icons to the navbar
Use pre or post on a menu entry. OINK includes the free local Font Awesome
assets:
menus:
main:
- name: Source
identifier: source
url: https://github.com/pgsty/oink
weight: 50
pre: <i class="fa-brands fa-github" aria-hidden="true"></i>
post: <span class="visually-hidden"> (external)</span>
Decorative icons need aria-hidden="true"; the link itself must retain a useful
text or accessible label. External links that open a new tab must use
rel="noopener".
Side navigation
The left panel on docs and blog pages is generated from the content hierarchy.
OINK orders entries by weight and uses linkTitle when present. Sections come
from _index.md files; translated sections need a peer _index.zh.md so their
navigation metadata is localized.
Hide a page from the sidebar with:
toc_hide: true
Hide it from a section landing-page summary with hide_summary: true. Set both
only when the page should be absent from both discovery surfaces.
Side-nav options
The common controls are:
params:
ui:
sidebar_menu_compact: true
sidebar_menu_foldable: true
sidebar_menu_truncate: 128
sidebar_cache_limit: 2000
sidebar_search_disable: false
sidebar_width_min: 220
sidebar_width_max: 480
sidebar_item_overflow: ellipsis
sidebar_menu_compactshows the active branch and nearby entries.sidebar_menu_foldablelets readers expand or collapse sections.sidebar_menu_truncatelimits entries and emits a build warning when the limit is too small.sidebar_cache_limitenables shared navigation markup above the configured site size.sidebar_width_minandsidebar_width_maxclamp the desktop drag-resizer.sidebar_item_overflowisellipsisby default; usewrapfor long labels.
The reader’s collapse state, width, and scroll position are preserved locally. The mobile view becomes a dismissible drawer with a backdrop and focus-safe controls.
Adding icons to the side nav
Set icon in page front matter:
---
title: Operations
icon: fa-solid fa-screwdriver-wrench
---
Use icons consistently across siblings. They are secondary cues, not a replacement for text labels.
Adding manual links to the side nav
Create a placeholder page at the desired position:
---
title: API status
weight: 90
manualLink: https://status.example.org/
manualLinkTitle: Live service status
manualLinkTarget: _blank
---
Use manualLinkRelref instead of manualLink for an internal content
reference; Hugo then fails the build if it cannot resolve the destination. OINK
adds noopener for new-tab links. Include a short body explaining the
destination because Hugo still generates a page for the placeholder.
Section as sidebar root (EXPERIMENTAL)
Enable rooted sidebars:
params:
ui:
sidebar_root_enabled: true
sidebar_root_menu: true
Then set a section’s _index.md:
---
title: API Reference v2
sidebar_root_for: self
sidebar_root_link_self: true
---
self applies the root to the section index and descendants; children keeps
the index in the parent tree but roots its descendants. The optional root menu
lets readers switch between roots. Rooted sections can nest, but redundant or
invalid values produce build warnings.
Table of contents (TOC)
Hugo builds the right-side page outline from Markdown headings. OINK renders it as a fixed documentation panel with quick links, language and theme controls, repository metadata, and taxonomy terms. Readers can collapse the panel; its state is stored locally.
Headings emitted by Markdown shortcodes ({{% ... %}}) participate in
Hugo’s table of contents. Headings emitted only by standard shortcodes
({{< ... >}}) generally do not, so content structure should remain in
Markdown whenever possible.
TOC customization
Hide the outline on one page:
notoc: true
Configure which heading levels Hugo includes:
markup:
tableOfContents:
startLevel: 2
endLevel: 4
ordered: false
Localize labels such as toc_on_this_page in the site’s i18n bundle. If custom
CSS changes the outline rail or fixed-panel dimensions, test active tracking,
zoom, keyboard focus, and pages with no headings.
Active TOC entry tracking with ScrollSpy
OINK uses a local Bootstrap ScrollSpy patch and IntersectionObserver to track the active heading. The workspace draws a continuous rail, active segment, and position marker. Disable tracking for a page with:
params:
ui:
scrollSpy:
disable: true
The legacy ScrollSpy configuration also accepts a global rootMargin. Changing
it affects when an entry becomes active and should be tested with short
sections, long sections, and direct fragment navigation.
Advanced ScrollSpy customization
Prefer configuration and project CSS. Overriding the ScrollSpy attribute partial
or docs-shell.js creates an implementation-level fork; add browser fixtures
for hash updates, back/forward navigation, resizing, reduced motion, and pages
that contain duplicate or missing IDs.
Breadcrumb navigation
Breadcrumbs are shown above ordinary content pages and in taxonomy results. Disable them globally:
params:
ui:
breadcrumb_disable: true
taxonomy_breadcrumb_disable: true
The same ui.breadcrumb_disable value can be set in a page or section cascade.
Breadcrumb labels come from localized page titles and must follow the same
logical hierarchy as the sidebar.
Heading self links
Enable OINK’s heading render hook in a consuming site:
{{ partial "td/render-heading.html" . }}
The generated .td-heading-self-link control uses # by default. It remains
visible on touch devices and appears on hover or focus for pointer devices. Keep
the link keyboard reachable and preserve a scroll offset that clears fixed
navigation.
Heading aliases and in-page targets
Changing a heading can break inbound fragment links. Treat its ID as a public route. To rename an ID, retain the old one as an empty anchor and set the new one explicitly:
## Quickstart <a id="get-started"></a> {#quickstart}
Use an empty <a id="..."></a> for an alias or other in-page target. Do not use
a span solely as a fragment target. IDs must be unique, stable, ASCII where
practical, and identical across language variants.
Quickstart
This live heading demonstrates that both #get-started and #quickstart reach
the same location. Translated headings should write the English rendered ID
explicitly rather than relying on language-specific automatic slug generation.
Implementation notes
- The document sets a global scroll offset for fixed chrome.
- Built-in block targets use
td-anchor-no-extra-offsetto avoid applying the additional offset twice. - The translation audit compares rendered heading IDs between English and Chinese pages.
- Removing an old alias is a breaking documentation change and needs a redirect or an explicitly documented compatibility decision.