Skip to content

Layouts and page types

Let type decide which shell a page uses, then adjust sidebar width and icons, outline depth, section index style and page width.

This page covers a page’s skeleton: whether it has a sidebar, how wide that is, how deep the outline goes, and whether a section index is a list or cards. Where content goes is in Organizing content; this page is only about the shell.

The rule is that the shell follows type, not the path. Documentation can live anywhere under content/ as long as it has type: docs.

Shell types

params.ui.shell_types lists the types that use the reading shell with a sidebar:

hugo.yml
params:
  ui:
    shell_types: [docs, book, blog, swagger]
type Shell
docs The documentation shell: left sidebar (section switcher + tree) + body + right-hand outline
book The documentation shell, plus numbered targets, the reading_width measure and the draft banner
blog The documentation shell, with the sidebar expanded by default and RSS as the left half of the title row
swagger The documentation shell, with the body handed to Swagger UI or Redoc — see API reference pages
Any other type An ordinary page: navbar + single-column body + footer, with no sidebar

Taxonomy and term pages are not in this table but use the same shell.

Assigning a type to a subtree uses a cascade, which is how documentation ends up at an arbitrary path:

content/handbook/_index.md
---
title: Operations handbook
type: docs
cascade:
  type: docs
---

Section roots are only navigation starting points

hugo.yml
params:
  ui:
    docs_section: docs
    blog_section: blog

These two keys do not decide the shell. They tell the theme where the documentation and blog trees are rooted, for resolving the sidebar root, quick links and default icons. The content/handbook/ example above still has the documentation shell, and leaving docs_section at docs does not affect it.

To make a docs page’s sidebar root the site home rather than the documentation section:

hugo.yml
params:
  ui:
    docs_sidebar_root: home # home | section

Those are the only two values, and anything else fails the build.

Documentation at the site root

A documentation-first site can publish the docs section at the URL root while the source stays in content/docs/. Three pieces of configuration are needed together.

The first uses Hugo’s own permalinks to drop the docs/ segment from URLs:

hugo.yml
permalinks:
  page:
    docs: /:sections[1:]/:slug/
  section:
    docs: /:sections[1:]

The second keeps the physical site root index usable as a link target while no longer competing for the same output path. Every language’s site root index (content/_index.md, content/_index.zh.md) needs it:

content/_index.md
---
title: Product Docs
build: { render: link }
---

The third declares the sidebar root to be the site home, so the sidebar and the pager share one tree:

hugo.yml
params:
  ui:
    sidebar_root_enabled: true
    docs_sidebar_root: home

With docs_sidebar_root: home, every top-level section of the site home enters that tree. Overview sections that are not part of the reading sequence — blog, community, download — opt out with toc_root: true in their own _index.md, which keeps them out of the tree and out of the paging order:

content/blog/_index.md
---
title: Blog
toc_root: true
---

Documentation then shares the URL root with blog, community and the rest. Build with --printPathWarnings and resolve every duplicate target before publishing.

Landing pages

Any page with layout: landing uses the landing layout: navbar + a body assembled from sections + footer, with no sidebar. How to write the data is in Home and landing pages.

hugo.yml
params:
  ui:
    landing_search: true

landing_search: false removes the search entry point from the landing shell and affects no other page.

The sidebar tree comes from the shape of content/, ordered by weight and labelled with linkTitle where one exists. What is adjustable is density and size:

hugo.yml
params:
  ui:
    sidebar_menu_compact: true
    sidebar_menu_foldable: true
    sidebar_menu_truncate: 2000
    sidebar_width_min: 220
    sidebar_width_max: 480
    sidebar_item_overflow: ellipsis # ellipsis | wrap
    sidebar_expand_levels: 2
  • sidebar_menu_compact expands only the current branch and its neighbours; false expands the whole tree.
  • sidebar_menu_foldable lets the reader expand and collapse sections manually. Blog sections are expanded by default; to collapse one by default, write sidebar_expanded: false in its _index.md.
  • sidebar_expand_levels is how many levels are expanded by default.
  • sidebar_menu_truncate is the maximum entries rendered per section, so a thousand-page tree does not inflate the HTML past usability.
  • sidebar_width_min / sidebar_width_max bound drag-resizing on the desktop, in pixels. The reader’s adjusted width is kept locally, and double-clicking the divider restores the default.
  • sidebar_item_overflow defaults to ellipsis (long titles truncate); a site with many long titles can use wrap.

Fold state, width and scroll position are stored locally per language. Below md the sidebar becomes a drawer with a backdrop.

To drop the sidebar on one page, use front matter:

content/docs/fullscreen-report.md
---
title: Full-screen report
sidebar_enabled: false
---

An explicit navigation tree, data/docs_nav.json

The sidebar tree is derived from content/ by default. A site may also supply an explicit navigation manifest, and the theme renders from it when three conditions hold together:

  • The site has a data/docs_nav.json containing a sections key;
  • The page’s type is docs or book;
  • The resolved sidebar root is not the site home.

The file is a nested node tree. Each node’s page points at a content path, url is its link, and children are its children; active_path_by_url records the ancestor chain for each URL, used for highlighting the current entry:

data/docs_nav.json
{
  "sections": [
    {
      "page": "/docs/start",
      "url": "/docs/start/",
      "children": [{ "page": "/docs/start/install", "url": "/docs/start/install/" }]
    }
  ],
  "active_path_by_url": {
    "/docs/start/install/": ["/docs/start/"]
  }
}

URLs have their language prefix stripped before comparison, so one file serves every language.

That tree also decides the paging order, so the sidebar and previous / next never disagree. An empty sections array fails the build (data/docs_nav.json does not define any Docs navigation sections), and a page pointing at a page that does not exist fails too (Docs navigation page not found). Placeholder nodes with manual_link and sidebar_divider rows stay in the sidebar without becoming paging targets.

It suits a site whose navigation order is generated by an external tool — a manual migrated from a Sphinx toctree that has to freeze its existing chapter order, say. Where order is maintained by weight in content/, the file is not needed.

An icon in a page’s front matter appears in the sidebar. Icons on every leaf page reduce readability, so a density policy controls them:

hugo.yml
params:
  ui:
    sidebar_icon_policy: groups # all | groups | none
Value Effect
all Every entry with an icon shows it (the compatibility default when unset)
groups Only the root and nodes with children show icons
none No entry icons in the sidebar

An invalid value only warns and falls back to all rather than failing the build. This site uses groups.

Book pages can expand an h2–h4 branch under the current sidebar row, which helps navigation inside a long chapter:

hugo.yml
params:
  ui:
    sidebar_headings: 3 # false | true | 2 | 3 | 4

An integer sets the deepest level expanded (2–4), true means 2 (h2 only), and false turns it off. Out of range fails the build. It applies to type: book pages only, and expands only under the current sidebar row.

Table of contents

The right-hand outline is generated by Hugo from the Markdown headings, and the levels collected are Hugo’s own configuration:

hugo.yml
markup:
  tableOfContents:
    startLevel: 2
    endLevel: 4
    ordered: false

The theme governs only the tracking behaviour:

hugo.yml
params:
  ui:
    scroll_spy: false

Scroll tracking is off by default. Set to true, the outline draws a continuous rail, highlights the current section and marks the position. The reader can collapse the right column entirely, and that state is kept locally. Below xl the right column is hidden and its content moves into the sidebar drawer.

To hide the outline on one page, use the front matter notoc: true.

Only headings that reach Hugo’s table of contents appear in the outline: headings emitted by a Markdown-form shortcode ({{% … %}}) do, and those from an ordinary shortcode ({{< … >}}) usually do not. Structural headings belong in the Markdown.

Section index style

A section with an _index.md lists its child pages automatically, in one of two styles:

hugo.yml
params:
  ui:
    section_index: cards # list | cards
    section_index_columns: 2
  • list (the default): one title plus description paragraph per child page;
  • cards: a grid of cards reading each child’s title (or linkTitle), description and icon.

It can be overridden per section, and an invalid value fails the build:

content/docs/components/_index.md
---
title: Components
section_index: cards
section_index_columns: 3
---

Related page-level switches: no_list: true lists no children; simple_list: true emits a bulleted list with no descriptions; and a child page with hide_summary: true removes itself from the list. Do not hand-write a child list: a hand-written one goes out of step with the sidebar.

Page width

hugo.yml
params:
  page_width: normal # normal | wide | full

normal is the usual reading width, wide widens the content column, and full fills the viewport. It can be overridden per page or per section; wide tables, large images and API reference pages often use wide:

content/docs/api/reference.md
---
title: API reference
page_width: wide
---

Book pages additionally have reading_width (slim / normal / wide), which changes the body’s own reading measure without touching the shell. An invalid value in either key fails the build.

Navbar and footer switches

The navbar and footer are per-page layout decisions, written at the top level of front matter (not under ui), and can be set once with a section cascade:

content/docs/_index.md
---
title: Docs
cascade:
  navbar_enabled: false
  footer_style: slim
---

The behaviour is in Navigation and menus and Brand and appearance, and the key definitions in Page parameters.

Verify

hugo --printPathWarnings --panicOnWarning
  • The build prints Total in … with no ERROR and no WARN;
  • A newly created type: docs page has a left sidebar. If not, check whether the cascade reaches that page and whether shell_types contains the type;
  • Drag the sidebar divider, reload and confirm the width persists; double-click restores the default;
  • Below md the sidebar becomes a closable drawer, and below xl the outline moves into the drawer;
  • A section index has as many cards as the sidebar has child pages;
  • A page with page_width: wide is wider than its neighbours;
  • With documentation at the site root, hugo --printPathWarnings reports no duplicate output paths.