Docs

Navigation

Help the user navigate your website using configurable navigation elements.

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 = true

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.

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 = 2

Example  

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);

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.

  Important

The sidebar recognizes multilingual data files. 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.

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.com

Menu 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: Third

You 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: Abbr

Auto-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  ):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
{{/* Derived from layouts/single.html template, but applied to all docs pages (list and single) */}}
{{/* Inserts auto-generated sidebar with section navigation */}}
{{/* Renders blocks inside main body */}}
{{ define "main" -}}
    {{- $breakpoint := partialCached "utilities/GetBreakpoint.html" site -}}
    {{- $padding := partial "utilities/GetPadding.html" -}}
    
    {{/* Auto-generated sidebar for docs */}}
    {{- $menu := .Scratch.Get "sidebar" -}}
    {{- if not (reflect.IsSlice $menu) }}{{ $none := dict }}{{ $menu = $none.missing }}{{ end }}
    {{- $version := .Scratch.Get "version" -}}
    {{- $sidebar := partial "page/sidebar-cached.html" (dict "page" . "menu" $menu "version" $version "auto-generate" true) -}}

    {{/* Render the offcanvas sidebar */}}
    {{- partial "page/sidebar-offcanvas.html" (dict "section" $.Section "raw" $sidebar) -}}

    {{/* Render the page content using responsive columns */}}
    <div class="container-xxl flex-fill {{ partial "utilities/PaddingClasses.html" (dict "padding" $padding "axis" "y") }} {{ partial "utilities/PaddingClasses.html" (dict "padding" $padding "axis" "x") }} px-xxl-0">
        <div class="row row-cols-1 row-cols-{{ $breakpoint.current }}-2 row-cols-{{ $breakpoint.next }}-3">
            <div class="col col-{{ $breakpoint.next }}-2 d-none d-{{ $breakpoint.next }}-block sidebar-overflow sticky-top p-0">
                {{ $sidebar | safeHTML }}
            </div>
            <div class="col-12 col-{{ $breakpoint.current }}-9 col-{{ $breakpoint.next }}-8 mb-5">
                {{/* Render the defined content blocks, using the default articles element as fallback for list pages */}}
                {{ if .Params.content_blocks }}
                    {{/* Blocks replace the page header here, so they own the h1 */}}
                    {{- partial "page/blocks.html" (dict "page" . "embed" true "heading-level" 1) -}}
                {{ else if eq .Kind "section" }}
                    {{ $.Scratch.Set "articlesParams" (dict
                        "sort"         "weight"
                        "reverse"      false
                        "class"        "card-emphasize border h-100"
                        "header-style" "none"
                        "icon-style"   "text-primary fa-xl"
                        "padding"      $padding.x
                    ) }}
                    {{ partial "page/articles.html" . }}
                {{ else }}
                    {{ .Render "header" }}
                {{ end }}

                {{ .Render "body" }}
                
                {{ .Render "footer" }}
            </div>
            <div class="col col-{{ $breakpoint.current }}-3 col-{{ $breakpoint.next }}-2 d-none d-{{ $breakpoint.current }}-block">
                {{ .Render "toc" }}
            </div>
        </div>
    </div>
{{ end -}}

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. Setting collapsible to true in 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 version attribute 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);
}

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:

markdown
{{< 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;
}