Docs
Navigation
Hinode supports several types of navigation that utilize Bootstrap elements. The main navigation is positioned on the top of the screen and includes a search input element. An optional secondary navigation is available as sidebar. Next to these navigation items, pages may optionally include a breadcrumb to indicate the current page’s location within the site’s navigational hierarchy. Finally, pages may also include a table of contents element that is linked to the scroll position of the current page.
Basic Configuration
The basic configuration of the navigation elements is set in the navigation section of the site parameters. The following settings are supported:
| Setting | Default | Description |
|---|---|---|
anchor |
- | Flag indicating to display anchor links on hovering a heading. |
| Flag indicating to display anchor links on hovering a heading. | ||
logo |
- | Address of the brand logo image, e.g. “/img/logo_embedded.svg”. The vector image file is expected to be present in your local static/img folder. |
Address of the brand logo image, e.g. “/img/logo_embedded.svg”. The vector image file is expected to be present in your local static/img folder. |
||
logo-mode |
false |
v1.15.0
Flag indicating if the brand logo should support color modes. If set, supply the base filename in logo (e.g. /img/logo.svg) and provide matching -light and -dark variants alongside it (e.g. logo-light.svg, logo-dark.svg); the navbar swaps between them automatically. |
v1.15.0
Flag indicating if the brand logo should support color modes. If set, supply the base filename in logo (e.g. /img/logo.svg) and provide matching -light and -dark variants alongside it (e.g. logo-light.svg, logo-dark.svg); the navbar swaps between them automatically. |
||
logo-align |
center |
v1.15.0
Alignment of the brand logo when the navbar is in collapsed mode, either start or center. |
v1.15.0
Alignment of the brand logo when the navbar is in collapsed mode, either start or center. |
||
logo-height |
30 | Height of the brand logo in pixels. |
| Height of the brand logo in pixels. | ||
color |
- | Theme Color of the navigation bar. Set the style to set the correct contrast of the menu items. The navigation bar is transparent when no color is set, but is set to the body color when scrolling to enhance the contrast. |
| Theme Color of the navigation bar. Set the style to set the correct contrast of the menu items. The navigation bar is transparent when no color is set, but is set to the body color when scrolling to enhance the contrast. | ||
style |
light |
Style of the navigation bar, either “light” or “dark”. It changes the colors of the menu items accordingly. |
| Style of the navigation bar, either “light” or “dark”. It changes the colors of the menu items accordingly. | ||
fixed |
false | Flag indicating to keep the navigation bar fixed at the top of the screen. |
| Flag indicating to keep the navigation bar fixed at the top of the screen. | ||
overlay |
false |
v0.23.0
Flag indicating to render the navigation bar as overlay on top of the page body. In this state, the navigation is transparent and the page body starts at the top of the viewport. The navigation becomes opaque when scrolling and is set to the specified theme color. |
v0.23.0
Flag indicating to render the navigation bar as overlay on top of the page body. In this state, the navigation is transparent and the page body starts at the top of the viewport. The navigation becomes opaque when scrolling and is set to the specified theme color. |
||
overlayMode |
dark |
v0.23.0
Style of the navigation bar when overlay is set, either “light” or “dark”. |
v0.23.0
Style of the navigation bar when overlay is set, either “light” or “dark”. |
||
horizontal |
false | v0.21.0 Flag indicating the second level navigation should render horizontally. By default, the navbar uses regular dropdown menus instead. |
| v0.21.0 Flag indicating the second level navigation should render horizontally. By default, the navbar uses regular dropdown menus instead. | ||
offset |
3em |
Applies an
offset to main elements
when fixed is set to true. |
Applies an
offset to main elements
when fixed is set to true. |
||
size |
md |
Optional breakpoint of the navbar toggler, either “xs”, “sm”, “md” (default), “lg”, or “xl”. |
| Optional breakpoint of the navbar toggler, either “xs”, “sm”, “md” (default), “lg”, or “xl”. | ||
fontsizeCollapsed |
6 | Font size of the menu items in collapsed state. The available sizes are [1..6], similar to the size of the headings [h1..h6]. |
Font size of the menu items in collapsed state. The available sizes are [1..6], similar to the size of the headings [h1..h6]. |
||
search |
true | v0.18.0 Flag indicating to include a search input. |
| v0.18.0 Flag indicating to include a search input. | ||
breadcrumb |
false | Flag indicating to add breadcrumb navigation to the top of any single page. List pages are excluded. |
| Flag indicating to add breadcrumb navigation to the top of any single page. List pages are excluded. | ||
toc |
true | Flag indicating to enable table of contents globally. Individual pages can override this setting in the frontmatter using the value includeToc, and a content type can override it with [pages.<type>] includeToc.
v3.15.0
Earlier versions ignored the frontmatter value for the sidebar table of contents, and ignored the per-type value for the dropdown shown on smaller screens. |
Flag indicating to enable table of contents globally. Individual pages can override this setting in the frontmatter using the value includeToc, and a content type can override it with [pages.<type>] includeToc.
v3.15.0
Earlier versions ignored the frontmatter value for the sidebar table of contents, and ignored the per-type value for the dropdown shown on smaller screens. |
||
sidebar |
true | Flag indicating to enable sidebar navigation globally. If set, a sidebar is displayed when applicable. |
| Flag indicating to enable sidebar navigation globally. If set, a sidebar is displayed when applicable. | ||
sidebarIcons |
false |
v3.13.0
Flag indicating to render an icon in front of sidebar entries derived from the page hierarchy, taken from each page’s icon frontmatter. Disabled by default, as icon is primarily used to illustrate a page’s card. Individual pages can override this setting in the frontmatter using the same name, and content types can set a default using pages.<type>.sidebarIcons. Sidebars defined by a data file are unaffected: they set an icon explicitly through the entry’s pre field. |
v3.13.0
Flag indicating to render an icon in front of sidebar entries derived from the page hierarchy, taken from each page’s icon frontmatter. Disabled by default, as icon is primarily used to illustrate a page’s card. Individual pages can override this setting in the frontmatter using the same name, and content types can set a default using pages.<type>.sidebarIcons. Sidebars defined by a data file are unaffected: they set an icon explicitly through the entry’s pre field. |
||
sidebarIconLevel |
1 |
v3.13.0
Deepest sidebar level that renders an icon when sidebarIcons is enabled. Levels are counted as displayed, so the default of 1 shows icons on the top-level entries only. As with sidebarIcons, a page can override the value in its frontmatter and a content type can set a default using pages.<type>.sidebarIconLevel. |
v3.13.0
Deepest sidebar level that renders an icon when sidebarIcons is enabled. Levels are counted as displayed, so the default of 1 shows icons on the top-level entries only. As with sidebarIcons, a page can override the value in its frontmatter and a content type can set a default using pages.<type>.sidebarIconLevel. |
||
language.icon |
fas globe |
v1.12.0 Icon to use for the language selector. |
| v1.12.0 Icon to use for the language selector. | ||
search.enabled |
true | v1.12.0 Flag indicating to include a search input. |
| v1.12.0 Flag indicating to include a search input. | ||
search.modal |
true | v1.12.0 Flag to trigger a modal input form for search queries and results. By default, an input field is added to the navbar instead. |
| v1.12.0 Flag to trigger a modal input form for search queries and results. By default, an input field is added to the navbar instead. | ||
search.icon |
“fas magnifying-glass” | v1.12.0 Icon to use for the modal search button. |
| v1.12.0 Icon to use for the modal search button. | ||
The below configuration shows the default configuration set in config/_default/params.toml.
[navigation]
anchor = true
logo = "/img/logo_icon.svg"
logo-mode = false
logo-align = "center"
logo-height = 30
color = "body"
fixed = true
overlay = false
overlayMode = "dark"
horizontal = false
offset = "5.7rem"
breadcrumb = true
toc = true
sidebar = true
sidebarIcons = false
sidebarIconLevel = 1
size = "md"
startLevel = 2
endLevel = 3
maxNumHeadings = 9
[navigation.padding]
x = 4
y = 4
[navigation.search]
enabled = true
modal = trueMain Navigation
The main navigation uses Hugo’s menu system to generate a responsive navigation bar at the top of the page. The navigation bar uses a breakpoint to add a toggler for smaller screens. A language switcher is added automatically if your site supports multiple languages. The language switcher links to the currently translated page if available.
Menus
The navigation bar uses Hugo’s menu system to generate the menu items. The navigation supports nesting at one level deep. Hinode supports three additional parameters:
| Argument | Required | Description |
|---|---|---|
alias |
No | If set, the active menu item is linked to the specific menu entry instead of being derived from the target URL. |
| If set, the active menu item is linked to the specific menu entry instead of being derived from the target URL. | ||
button |
No | v0.24.10 If set, renders the menu item as a (small) button. |
| v0.24.10 If set, renders the menu item as a (small) button. | ||
spacing |
No | v0.24.10 If set, inserts a spacer. All prior menu items are left aligned, whilst the remaining menu items are right aligned. |
| v0.24.10 If set, inserts a spacer. All prior menu items are left aligned, whilst the remaining menu items are right aligned. | ||
The following example defines an example menu configuration called sample (the main configuration is called main).
[[sample]]
name = "Tutorials"
pageRef = "/tutorials/"
weight = 10
# [[sample]]
# name = "Blocks"
# pageRef = "/blocks/"
# weight = 20
# [[sample]]
# name = "Approach"
# pageRef = "/blocks/approach/"
# parent = "Blocks"
# weight = 1
# [[sample]]
# name = "Releases"
# pageRef = "/blocks/releases/"
# parent = "Blocks"
# weight = 2Example
The example below illustrates the navigation bar based on the sample configuration defined in the previous paragraph. The About and About (no alias) menu entries illustrate the behavior of the alias parameter. The GitHub menu entry shows an icon to indicate it is an external link and opens in a new window.
Versioning
The main navigation supports versioning too. When configured, a drop-down menu containing the available versions is automatically added. See the Documentation Configuration for more details.
Customization
Hinode includes a navigation bar at the top of the screen by default. You can modify the configuration in the layouts/_default/baseof.html file. The navigation bar is also available as
Shortcode
.
The following variables are available to modify the styling of the horizontal navigation. You can override them in assets/scss/theme/_variables.scss.
$dropdown-transition: opacity .15s ease-in-out !default;
$dropdown-horizontal-margin-top: calc((-1.5 * 1rem) - 2px);
$dropdown-horizontal-padding-y: calc(1rem + 2px);Sidebar Navigation
Hinode supports optional sidebar navigation. It is intended to be used as companion to the main navigation and is typically used in content-heavy sections, such as documentation pages. On smaller screens, the sidebar is replaced with an offcanvas element . In this case, the main navigation receives an additional toggler on the left of the screen.
Menus
Important
The sidebar recognizes multilingual data files. Add the language code as suffix to your data file. For example,
data/blog.en.ymldefines the sidebar menu of the English translation of the blog section.
Inspired by Bootstrap’s documentation site, Hinode uses a separate configuration file for the sidebar menus. A sidebar can be configured for each main section of the site. For example, the sidebar menu of the docs section is defined in data/docs.yml. The sidebar menu supports group items and single page items. You can optionally provide an internal or external link for the destination page (see the
Link Shortcode
for its behavior). By default, Hinode derives the destination from the menu item. The below example defines a group section called Getting started with three siblings. A single page About is added next. The latter redirects to an external website.
- title: Getting started
pages:
- title: Introduction
- title: Commands
- title: Contribute
- title: About
link: https://example.comMenu items can be nested within each other. The below example defines three content pages at the relative path A/B/C. The navigation path should be similar to the slug of the individual pages.
- title: A
pages:
- title: B
pages:
- title: C
pages:
- title: First
- title: Second
- title: ThirdYou can define separate data files for each available language in a multilingual site. Add the language code as suffix to your data file. For example, data/blog.en.yml defines the sidebar menu of the English translation of the blog section. Hinode uses data/blog.yml as fallback (or any other
data format supported by Hugo
).
Since
v0.27.27
, Hinode renders a link for group items when a matching list page is found. You can suppress this behavior for a group item by setting its link value to "#" (the value is double-quoted to ensure the value is not interpreted as a YAML comment). For example, the Hinode docs includes a group item Components. The link /docs/components/ redirects to a list page that shows all available Hinode components. This list page breaks out from the sidebar navigation layout. To avoid navigating to this page, the Hinode docs suppresses this link. Instead, the group item is expanded or collapsed, pending current state.
- title: Components
link: "#"
pages:
- title: AbbrAuto-Generation
Since v2.0.0, the sidebar can automatically generate its menu from the pages in the current section, without requiring a manual data file. The docs layout enables this by default. When a manual data file is present, it takes precedence over auto-generation. The following layout illustrates how to enable auto-generation of the sidebar partial (it is also available in the
Hinode reopository
):
|
|
The following arguments control auto-generation behavior. They are passed to the assets/sidebar.html partial — for example, inside a custom layout that calls it explicitly.
| Argument | Default | Description |
|---|---|---|
auto-generate |
false | v2.0.0 Flag to auto-generate the sidebar from all pages in the current section. Takes effect only when no manual data file is found. |
| v2.0.0 Flag to auto-generate the sidebar from all pages in the current section. Takes effect only when no manual data file is found. | ||
nested |
true |
v2.0.0
Include child pages recursively. When set to false, only the immediate children of the section are listed. |
v2.0.0
Include child pages recursively. When set to false, only the immediate children of the section are listed. |
||
reverse |
false | v2.0.0 Reverse the sort order. |
| v2.0.0 Reverse the sort order. | ||
sort |
weight |
v2.0.0
Field used to sort pages. Common values: weight, title, date. |
v2.0.0
Field used to sort pages. Common values: weight, title, date. |
||
level-min |
0 | Minimum menu depth to render (1-based). 0 means no minimum. Use together with level-max to restrict which levels are shown. |
Minimum menu depth to render (1-based). 0 means no minimum. Use together with level-max to restrict which levels are shown. |
||
level-max |
0 | Maximum menu depth to render (1-based). 0 means no maximum. Set to 1 to show only top-level entries (enables the collapsible icon-only mode). |
Maximum menu depth to render (1-based). 0 means no maximum. Set to 1 to show only top-level entries (enables the collapsible icon-only mode). |
||
collapsible |
false | When true, renders a toggle button that collapses the sidebar to icon-only view. Only effective when level-max is set to 1. |
When true, renders a toggle button that collapses the sidebar to icon-only view. Only effective when level-max is set to 1. |
||
The combination of level-min and level-max selects one of three rendering modes:
- Full tree (default, both
0): the complete page hierarchy is rendered from the root. - Top-level only (
level-max = 1): only first-level entries are shown. Settingcollapsibletotruein this mode adds a toggle button that collapses the sidebar to icon-only view. - Active subtree (
level-min ≥ 2): only the children of the currently active top-level group are shown, useful for nested documentation layouts.
Page-Level Control
Individual pages can override sidebar behavior using frontmatter parameters.
| Frontmatter parameter | Description |
|---|---|
excludeFromSidebar |
Set to true to exclude the page from an auto-generated sidebar. Has no effect on manually defined data-file menus. |
Set to true to exclude the page from an auto-generated sidebar. Has no effect on manually defined data-file menus. |
|
sidebar-menu |
Set to false to disable the sidebar entirely for this page. No sidebar is rendered regardless of site-wide configuration. |
Set to false to disable the sidebar entirely for this page. No sidebar is rendered regardless of site-wide configuration. |
---
title: My Page
excludeFromSidebar: true # hide from auto-generated sidebar
sidebar-menu: false # disable sidebar for this page entirely
---Versioning
Note
Release v0.15.0 of Hinode automatically detects the version of the current context. The configuration of the
versionattribute is no longer needed.
You can create versioned files for your menu data by adding a version suffix to the data file. For example, data/docs-1.0.yml contains the menu data for the docs menu of version 1.0. Hinode uses data/docs.yml as default sidebar navigation data when no versioned file is available. See the
Documentation Configuration
for additional options.
Customization
The file assets/scss/components/_sidebar.scss contains the styling of the sidebar. It refers to a button $btn-toggle that is defined in assets/scss/common/_icons.scss. It also defines the spacing to be added to the page’s main content section when using a fixed navigation bar:
.sidebar {
top: var(--navbar-offset);
}
// Brand block at the top of the sidebar (Mode A only). Two images are
// rendered; visibility flips with the collapsed state so the label-style
// transition stays consistent.
.sidebar-brand {
display: flex;
align-items: center;
justify-content: flex-start;
padding: 0.5rem 0.5rem 0.75rem;
line-height: 1;
.sidebar-brand-full,
.sidebar-brand-mark {
max-width: 100%;
height: auto;
}
.sidebar-brand-full {
opacity: 1;
}
.sidebar-brand-mark {
display: none;
opacity: 0;
}
}
.sidebar-collapsed .sidebar-brand,
html.sidebar-pre-collapsed .sidebar-collapsible .sidebar-brand {
justify-content: center;
padding-left: 0;
padding-right: 0;
.sidebar-brand-full {
display: none;
opacity: 0;
}
.sidebar-brand-mark {
display: inline-block;
opacity: 1;
}
}
.sidebar-overflow {
top: calc(var(--navbar-offset) + 1rem);
max-height: calc(100vh - var(--navbar-offset));
overflow-y: auto;
scrollbar-gutter: stable;
// `scrollbar-gutter: stable` reserves the gutter only for classic
// scrollbars. On macOS, Chrome/Safari default to overlay scrollbars,
// which take no layout space — so the gutter collapses to 0 and the bar
// floats over the sidebar content (the group chevrons). Styling the
// scrollbar opts the element out of overlay mode, giving a non-overlay bar
// that reserves width and sits in the stable gutter, consistent across
// browsers. A muted thumb on a transparent track keeps it subtle and
// light/dark aware.
@include scrollbar(
$foreground-color: var(--bs-border-color),
$background-color: transparent,
$size: 8px
);
}
.sidebar-item {
--bs-border-radius: #{$theme-border-radius};
--bs-border-radius-sm: #{$theme-border-radius};
--bs-border-radius-lg: #{$theme-border-radius};
--bs-border-radius-xl: #{$theme-border-radius};
--bs-border-radius-xxl: #{$theme-border-radius};
color: rgba(0, 0, 0, 0.65);
margin-left: 0 !important;
display: block;
padding: 0.1875rem 0.5rem !important;
&.active {
color: $primary;
}
&:hover,
&:focus {
color: $primary;
background-color: tint-color($primary, 90%);
}
}
.sidebar-item-group {
--#{$prefix}link-opacity: 0.65;
border-radius: #{$theme-border-radius};
&:hover,
&:focus {
color: $primary;
background-color: tint-color($primary, 90%);
}
> div > a {
display: block;
width: 100%;
color: rgba(var(--#{$prefix}body-color-rgb), var(--#{$prefix}link-opacity, 1));
text-decoration: none;
cursor: pointer;
&:hover,
&:focus {
color: $primary;
}
}
}
.sidebar-item-group > div {
padding: 0.1875rem 0 0.1875rem 0.5rem !important;
}
// Hanging indent for rows that lead with an icon.
//
// A sidebar row is a single block-level anchor holding an inline icon followed by
// the label text, so a label long enough to wrap starts its second line at the
// anchor's content edge - underneath the icon - instead of under the first line's
// text. The icon occupies a fixed column (the template renders it `.fa-fw`, which
// is 1.25em) and `.me-1` supplies the gap to the label, so the two can be
// reclaimed as a hanging indent: pad the block out by that width and pull the
// first line back by the same amount. Single-line rows are unaffected - the
// padding and the outdent cancel - and only continuation lines move.
//
// The width is repeated here rather than shared through a variable because it
// mirrors the utility classes the template emits, not a design knob: retuning it
// without changing `assets/sidebar.html` would only break the alignment.
$sidebar-item-icon-column: calc(1.25em + #{$spacer * 0.25});
// Scoped three ways:
//
// * `:has()` on a leading icon, because a row without one must keep its text
// flush with the padding edge - a blanket rule would outdent its first line.
// * Not `.d-flex`, the shape a downstream row with a trailing suffix icon uses
// (see the collapsed-mode block below). `text-indent` does not apply to a
// flex container, so the padding would shift such a row without the outdent
// to cancel it - and a flex row already aligns its wrapped text correctly.
// * Not a collapsible sidebar. There the label is a `.sidebar-item-label` span
// that is `white-space: nowrap` by design, so it never wraps; its gap to the
// icon is `.me-1` plus the span's own `.ms-1`, so this width would be wrong;
// and its geometry is sequenced by the collapse choreography below, which a
// padding change lands outside of.
.sidebar:not(.sidebar-collapsible) {
// `padding-left` needs `!important` only because the base `.sidebar-item`
// padding above carries one.
.sidebar-item:not(.d-flex):has(> svg:first-child, > i:first-child) {
padding-left: calc(0.5rem + #{$sidebar-item-icon-column}) !important;
text-indent: calc(-1 * #{$sidebar-item-icon-column});
}
// Group headers carry no padding of their own - it sits on the wrapping
// `.sidebar-item-group > div` - so the indent is the icon column alone.
.sidebar-item-group > div > a:has(> svg:first-child, > i:first-child) {
padding-left: $sidebar-item-icon-column;
text-indent: calc(-1 * #{$sidebar-item-icon-column});
}
}
.btn-toggle-group {
padding: 0.25rem 0.5rem;
font-weight: 600;
color: rgba(0, 0, 0, 0.65);
background-color: transparent;
&:hover,
&:focus {
background-color: transparent;
}
&::before {
width: 1.25em;
line-height: 0;
content: $btn-toggle;
transition: transform 0.35s ease;
transform-origin: 0.5em 50%;
}
}
.btn-toggle-group[aria-expanded="true"] {
&::before {
transform: rotate(90deg);
}
}
@if $enable-dark-mode {
@include color-mode(dark) {
.sidebar-item {
--#{$prefix}link-opacity: 0.65;
color: rgba(var(--#{$prefix}body-color-rgb), var(--#{$prefix}link-opacity, 1));
&.active {
color: $primary-text-emphasis-dark !important;
}
&:hover,
&:focus {
color: $primary-text-emphasis-dark !important;
background-color: transparent;
box-shadow: inset 0 0 0 1px $primary-bg-subtle-dark;
}
}
.sidebar-item-group {
color: var(--bs-body-color) !important;
&.active {
color: $primary-text-emphasis-dark !important;
}
&:hover,
&:focus {
color: $primary-text-emphasis-dark !important;
background-color: transparent;
box-shadow: inset 0 0 0 1px $primary-bg-subtle-dark;
}
> div > a {
&:hover,
&:focus {
color: $primary-text-emphasis-dark !important;
}
}
}
.btn-toggle-group {
color: var(--bs-body-color);
&:hover,
&:focus {
background-color: transparent;
}
&::before {
content: $btn-toggle-dark;
}
}
.btn-toggle-group[aria-expanded="true"] {
color: var(--bs-secondary-color);
}
}
}
// No-JS fallback: the collapse trail and active highlight are applied client-side by
// sidebar-active.js, and Bootstrap's collapse toggles require JavaScript. Without JavaScript
// (the `no-js` class set in baseof.html is swapped for `js` by critical/js-detect.js) render
// every sidebar group expanded instead.
.no-js .sidebar .collapse {
display: block;
}
// Collapsible sidebar
.sidebar-no-transition,
.sidebar-no-transition * {
transition: none !important;
}
html.sidebar-pre-collapsed .sidebar-collapsible .sidebar-item-label {
max-width: 0 !important;
opacity: 0 !important;
margin-inline-start: 0 !important;
}
.sidebar-toggle-btn {
color: rgba(0, 0, 0, 0.65);
&:hover {
color: $primary;
}
.sidebar-icon-toggle {
transition: transform 0.4s ease;
}
}
// Cap the label at the room actually left beside the icon.
//
// `$sidebar-label-max-width` is a fixed length because the collapse transition
// needs one, which leaves it blind to the track it sits in. The label is an
// inline-block that neither shrinks to fit nor is clipped by its row, so once
// the icon and a full-width label no longer fit side by side, the whole label
// box drops onto a second line underneath the icon - doubling the row height -
// and in a narrower track it then spills out past the sidebar's edge. Hinode
// does not set the sidebar's width (a downstream theme does, see
// `.app-shell-sidebar`), so the fixed cap and the real track are free to
// disagree; measured against a 6-row menu, rows start dropping at a 240px track
// and start spilling at 208px.
//
// `min()` keeps the fixed cap wherever it still fits and yields to the
// remaining room where it does not, so a track of 256px or wider renders
// exactly as before. The row publishes its own budget as a custom property so
// the label keeps one low-specificity `max-width` declaration and the collapsed
// states below still override it on the cascade. The subtrahends are the
// label's own `.ms-1` and, for a row that leads with an icon, the icon column.
.sidebar-item {
--sidebar-label-max: min(#{$sidebar-label-max-width}, calc(100% - #{$spacer * 0.25}));
&:has(> svg:first-child, > i:first-child) {
--sidebar-label-max: min(
#{$sidebar-label-max-width},
calc(100% - #{$sidebar-item-icon-column} - #{$spacer * 0.25})
);
}
}
.sidebar-item-label {
display: inline-block;
max-width: var(--sidebar-label-max, #{$sidebar-label-max-width});
overflow: hidden;
white-space: nowrap;
vertical-align: middle;
// Geometry rides the shared collapse timing; the fade is deliberately
// shorter so the label is gone before the track finishes narrowing.
transition:
max-width $sidebar-collapse-transition-duration $sidebar-collapse-transition-easing,
opacity 0.15s ease,
margin-inline-start $sidebar-collapse-transition-duration $sidebar-collapse-transition-easing;
}
.sidebar-collapsed {
.sidebar-item-label {
max-width: 0;
opacity: 0;
margin-inline-start: 0 !important;
}
.sidebar-toggle-btn .sidebar-icon-toggle {
transform: rotate(180deg);
}
}
// Collapsed-mode refinements:
// * Items use white-space: nowrap so the (max-width: 0) label inline-block
// stays on the same line as the icon. Without this the label wraps to a
// second line at narrow widths and silently doubles each row's height.
// * Toggle container and menu items center horizontally so the icon column
// reads as a single vertical track.
// * The icon's `.me-1` trailing margin is zeroed so it truly centers (the
// utility otherwise pulls each icon 4px to the left).
.sidebar-collapsed,
html.sidebar-pre-collapsed .sidebar-collapsible {
// The base template emits a `.ps-1` (padding-left: 0.25rem) on the nav
// for the expanded layout's hanging indent. In icon-only mode that
// asymmetric padding shifts the icon column off-centre — zero it out.
&.sidebar {
padding-left: 0 !important;
}
.sidebar-toggle-container {
justify-content: center !important;
padding-right: 0 !important;
}
.sidebar-item {
white-space: nowrap;
overflow: hidden;
text-align: center;
}
// `text-align: center` doesn't apply to flex layouts. Items rendered with
// `.d-flex` (e.g. a sidebar entry with a trailing suffix icon) need their
// (now zero-width) flex children grouped via `justify-content: center`.
.sidebar-item.d-flex {
justify-content: center;
}
.sidebar-item .me-1 {
margin-right: 0 !important;
}
}
// Sequence the centring refinements above behind the geometry they belong to.
//
// They are position changes, not animations: `text-align` and `justify-content`
// are discrete, and the paddings flip in one step. So they all land on the tick
// the collapsed class flips, while the labels (and, in a downstream theme, the
// column width) are still mid-collapse. Centring an icon+label pair inside a
// still-full-width column throws the icon far to the right for one frame — ~50px
// in a 16rem column — after which it sweeps back past its resting place as the
// column narrows, because the icon sits at `(columnWidth - contentWidth) / 2` and
// those two shrink on different curves. Three direction changes for a gesture
// that should read as one motion.
//
// A zero-duration transition with a delay defers a value change without animating
// it, and `transition-behavior: allow-discrete` lets the keyword properties take
// part at all. The delay is the shared collapse duration, so the refinements land
// exactly as the labels and the column finish: the icon holds still throughout and
// steps once into its centred position. The gesture is not lengthened.
//
// Only the collapsed state carries the delay. A transition takes its timing from
// the after-change style, so collapsing defers and expanding does not — which is
// what expand needs: the refinements have to be gone before the column widens, or
// the now icon-only rows would re-centre in an ever-wider track and sweep right.
//
// `white-space` and `overflow` carry the mirror image of that delay, because the
// direction that needs them held is the opposite one. They are the clipping pair:
// `nowrap` keeps the (max-width: 0) label on the icon's line and `hidden` stops it
// spilling out of a column narrower than its text.
//
// Collapsing, they must land at once — the label is still full width on the tick
// the class flips, so anything later lets it wrap. Expanding, landing at once is
// exactly the bug: `nowrap` is *removed* on that tick while the column is still
// widening, so for a few frames the label sits in a track too narrow to hold it,
// wraps to a second line, and doubles every row's height until the width catches
// up. Measured at 1440x900: rows peak at 54.77px against a settled 30.77px, with
// the column only 140px wide and the label already 82px.
//
// So the delay goes on the expanded state, not the collapsed one — the inverse of
// the centring refinements above. Same mechanism, opposite assignment: the
// after-change style supplies the timing, so the clipping pair defers on expand
// and lands immediately on collapse, and both directions hold their row height.
//
// Progressive enhancement: without `transition-behavior` every property here lands
// immediately, exactly as it did before this block existed.
@supports (transition-behavior: allow-discrete) {
.sidebar-collapsible {
&.sidebar {
transition: padding-left 0s;
}
.sidebar-toggle-container {
transition: justify-content 0s allow-discrete, padding-right 0s;
}
.sidebar-item {
transition: $sidebar-collapse-refinement-transition, $sidebar-collapse-clipping-transition-delayed;
}
.sidebar-item .me-1 {
transition: margin-right 0s;
}
}
.sidebar-collapsed {
&.sidebar,
.sidebar-toggle-container,
.sidebar-item .me-1 {
transition-delay: $sidebar-collapse-transition-duration;
}
.sidebar-item {
transition: $sidebar-collapse-refinement-transition-delayed, $sidebar-collapse-clipping-transition;
}
}
}
// Crossfade the two brand marks rather than cutting between them.
//
// The brand swap is a `display` change, so it is discrete by nature: the full
// logo vanishes and the mark appears on the same tick the collapsed class
// flips, while the column is still 16rem and narrowing. Deferring the swap to
// the end of the gesture does not fix that — a later hard cut is still a hard
// cut. Overlapping the two with opacity is what makes it read as one motion.
//
// Three pieces are needed:
//
// * The brand becomes a single-cell grid so both marks share one origin.
// While both are rendered a flex row would place them side by side and the
// brand would visibly double in width mid-gesture.
// * `display` joins the transition with `transition-behavior: allow-discrete`,
// which keeps the outgoing mark rendered for the whole fade instead of
// dropping it on the tick, and `@starting-style` gives the incoming mark a
// value to fade up from.
// * The brand's own centring is deferred behind the collapse, exactly as the
// item rows' is above. Without it `justify-content: center` lands on the
// click tick and the fade happens at the centre of a column that is still
// full width — the mark appears ~100px to the right of the logo it is
// replacing and then slides back as the column narrows. Deferred, the two
// marks sit at the same x for the whole fade.
//
// Rest states are unchanged: exactly one mark is rendered at rest, so the grid
// track is sized by it alone and the layout matches the `display` swap this
// replaces. Both directions are covered — a transition takes its timing from
// the after-change style, and the fade is symmetric, so only the deferral of
// the centring is one-directional (collapse defers, expand does not, for the
// same reason the item rows' does).
//
// Progressive enhancement: without `transition-behavior` the whole block is
// skipped, the brand stays a flex row and the marks cut over exactly as before.
@supports (transition-behavior: allow-discrete) {
.sidebar-collapsible .sidebar-brand {
display: grid;
transition: justify-content 0s allow-discrete, padding-left 0s, padding-right 0s;
// Stack every child in the one cell. The brand renders each mark
// through `assets/image.html`, which wraps it when light and dark
// variants are supplied — so match the children, not the marks.
> * {
grid-area: 1 / 1;
}
// `@starting-style` competes on specificity like any other rule, so the
// starting value has to be declared at least as specifically as the
// `opacity: 1` it fades up from. Scoped one class shorter, this loses to
// the collapsed-state rule and the incoming mark appears at full opacity
// on collapse while only the outgoing one fades.
.sidebar-brand-full,
.sidebar-brand-mark {
transition:
opacity $sidebar-collapse-transition-duration $sidebar-collapse-transition-easing,
display $sidebar-collapse-transition-duration allow-discrete;
@starting-style {
opacity: 0;
}
}
}
.sidebar-collapsed .sidebar-brand {
transition-delay: $sidebar-collapse-transition-duration;
}
}
@if $enable-dark-mode {
@include color-mode(dark) {
.sidebar-toggle-btn {
color: var(--bs-body-color);
&:hover {
color: $primary-text-emphasis-dark;
}
}
}
}
// Secondary group row: stack vertically (button above link) when sidebar is icon-only
.sidebar-collapsed .sidebar-secondary-row,
html.sidebar-pre-collapsed .sidebar-secondary-row {
flex-direction: column-reverse;
align-items: center;
}
// Secondary group — icon rotates down (180°) when collapsed, up (0°) when expanded
.sidebar-secondary-icon {
transition: transform 0.2s ease;
transform: rotate(180deg);
}
.sidebar-secondary-toggle[aria-expanded="true"] .sidebar-secondary-icon {
transform: rotate(0deg);
}
// Secondary group toggle button
.sidebar-secondary-toggle {
color: rgba(0, 0, 0, 0.65);
padding: 0.1875rem 0.5rem;
border-radius: #{$theme-border-radius};
&:hover,
&:focus {
color: $primary;
background-color: tint-color($primary, 90%);
}
}
@if $enable-dark-mode {
@include color-mode(dark) {
.sidebar-secondary-toggle {
color: rgba(var(--#{$prefix}body-color-rgb), 0.65);
&:hover,
&:focus {
color: $primary-text-emphasis-dark;
background-color: transparent;
box-shadow: inset 0 0 0 1px $primary-bg-subtle-dark;
}
}
}
}
// Flex column sidebar so .sidebar-spacer pushes the secondary group to the bottom
// and .sidebar-footer (always-visible items) stays glued to the nav's bottom edge.
@include media-breakpoint-up(md) {
.sidebar-has-spacer {
display: flex;
flex-direction: column;
min-height: 100%;
> ul.list-unstyled {
flex: 1;
display: flex;
flex-direction: column;
}
}
.sidebar-spacer {
flex: 1 1 0;
min-height: 1rem;
}
}
// When the sidebar contains a .sidebar-footer the nav becomes a flex column so
// the footer is always anchored to the bottom regardless of content height.
.sidebar:has(> .sidebar-footer) {
display: flex;
flex-direction: column;
min-height: 100%;
> ul.list-unstyled {
flex: 1 1 auto;
}
}
.sidebar-footer {
margin-top: auto;
}
// Sidebar footer line: keep the always-visible item (e.g. "Infusal Status")
// vertically centred on the same baseline as the page footer's copyright line
// in BOTH the expanded and collapsed states. The link is shorter than the
// secondary toggle, so centring (rather than bottom-anchoring) is what lands its
// icon on the footer baseline. `min-height` holds the row at the toggle's height
// so that baseline is identical whether or not the toggle shares the line, and
// `position: relative` is the positioning context the collapsed toggle is lifted
// into (see below).
.sidebar-footer .sidebar-secondary-row {
align-items: center !important;
position: relative;
min-height: 2rem;
}
// Collapsed (icon-only) mode: the link is the single in-flow child and stays
// centred on the footer line, while the secondary toggle is lifted out of flow
// and stacked directly above it. This keeps the always-visible icon anchored to
// the page-footer baseline and stops the two stacked icons from inflating the
// footer band (the toggle overflows up into the empty space above the footer).
.sidebar-collapsed .sidebar-footer .sidebar-secondary-row,
html.sidebar-pre-collapsed .sidebar-collapsible .sidebar-footer .sidebar-secondary-row {
flex-direction: row;
align-items: center !important;
}
.sidebar-collapsed .sidebar-footer .sidebar-secondary-toggle,
html.sidebar-pre-collapsed .sidebar-collapsible .sidebar-footer .sidebar-secondary-toggle {
position: absolute;
bottom: 100%;
left: 50%;
transform: translateX(-50%);
}
// Align a group's children with its label rather than its icon.
//
// A leading icon pushes the parent's own label right by the icon's width plus
// its `me-1` gutter. `ps-3` is narrower than that, so a child list using it
// sits slightly to the *left* of the label it belongs to and the hierarchy
// reads as broken. Groups with an icon get this class from the template in
// place of `ps-3`; groups without one are untouched.
//
// The template sets it rather than CSS deriving it with `:has()`. The collapse
// trail is expanded client side, and a selector that must be re-resolved as a
// subtree goes from display:none to visible can paint the old indent first and
// correct it a frame later, which reads as the sidebar shifting on load.
.sidebar-nav-icon-indent {
// Matches `fa-fw` (--fa-width: 1.25em) plus the icon's `me-1` gutter.
padding-left: calc(1.25rem + 0.25rem);
}Breadcrumb
Hinode supports optional breadcrumb navigation. The breadcrumb indicates the current page’s location within the site’s navigational hierarchy. It is automatically populated by Hugo. Enable the breadcrumb in the Basic Navigation Configuration . If enabled, all single pages will add breadcrumb navigation to the top of the page.
Example
When enabled, the breadcrumb looks like this:
{{< breadcrumb path="breadcrumb" >}}Customization
The breadcrumb is also available as Shortcode .
Table of Contents
Single pages can optionally show an table of contents on the right of the screen. The table of contents is automatically populated based on the headings within the page content (two levels deep). The table of contents is hidden if it has less than two items. On smaller screens, a drop-down menu is added to the top of the page. Enable the table of contents in the Basic Navigation Configuration . If enabled, all single pages will show the element, unless disabled in the page’s frontmatter.
Customization
The file assets/scss/components/_toc.scss defines the styling of the table of contents element. It adds spacing to align the element to the sidebar, amongst other styling:
.toc-sidebar {
top: calc(var(--navbar-offset) + 1rem);
max-height: calc(100vh - var(--navbar-offset));
overflow-y: auto;
right: 0;
z-index: 2;
}