projectious.work · Documentation v0.3.1
projectious.work · v0.3.1

Documentation

Install, configure, author, extend and maintain the projectious.work Hugo theme.

Printed August 26, 2026 · 22 pages

Contents

  1. Getting started
  2. Site-wide configuration
  3. Versioned documentation
  4. Page configuration (front matter)
  5. Template authoring guide
  6. Tokens and public API
  7. Terminal recordings
  8. Tailwind and design tokens
  9. Shortcodes
  10. Tags
  11. Developer guide
  12. Search
  13. Maintenance and upgrades
  14. Mathematics
  15. Dependencies and SBOM
  16. Jupyter notebooks
  17. Internationalization
  18. Header and navigation
  19. Editing and feedback
  20. Diagrams
  21. Code blocks
  22. Accessibility

Getting started

Create a Hugo site from scratch, install the theme from a release or local checkout, configure its outputs and run it.

This is a from-scratch walkthrough for a new site. Existing Hugo sites can begin at Choose how to install the theme.

1. Install the tools#

Install Hugo 0.128.0 or newer, Go, Node.js/npm and Git. Confirm them before creating files:

sh
hugo version
go version
node --version
npm --version
git --version

The example is verified with Hugo 0.164.0. Use Hugo Extended because the CSS pipeline invokes Tailwind through Hugo Pipes.

2. Create an empty site#

sh
hugo new site example-docs
cd example-docs
git init
hugo mod init example.com/example-docs
npm init -y
npm install --save-dev @tailwindcss/cli@^4.1.0

hugo new site creates hugo.toml, content/, layouts/, static/ and other standard Hugo directories. hugo mod init creates go.mod, which records the theme dependency.

3. Choose how to install the theme#

Released Hugo Module — recommended#

Add the module import to the new site’s hugo.toml:

hugo.toml
[module]
  [[module.imports]]
    path = "github.com/projectious-work/brand-theme-hugo-vanilla"

Fetch and pin a released version:

sh
hugo mod get github.com/projectious-work/brand-theme-hugo-vanilla@v0.3.1
hugo mod tidy

Commit go.mod and go.sum. Production sites should use a release tag, not a moving branch.

Local checkout — offline development or theme changes#

Clone or unpack the theme beside the site:

text
workspace/
├── brand-theme-hugo-vanilla/
└── example-docs/

Keep the same module import, then add a local replacement to the site’s go.mod:

go.mod
replace github.com/projectious-work/brand-theme-hugo-vanilla => ../brand-theme-hugo-vanilla

Hugo now reads the local checkout without downloading the module. Do not commit a machine-specific replace line in a production site; remove it and run hugo mod tidy before releasing.

4. Configure the feature outputs#

Copy the complete commented configuration from Configuration into the site’s root hugo.toml—the file created by hugo new site. The output-format blocks enable search, print views, page Markdown and llms.txt; the markup block enables class-based syntax highlighting, heading tables of contents and math.

5. Create the first pages#

text
content/
├── _index.md
└── docs/
    ├── _index.md
    └── first-page.md
content/_index.md
+++
title = "Example documentation"
tagline = "Documentation for the example product."
+++
content/docs/_index.md
+++
title = "Documentation"
description = "Product and operations documentation."
weight = 10
+++
content/docs/first-page.md
+++
title = "First page"
description = "The first page in the documentation set."
weight = 10
icon = "book"
+++

Write the page in ordinary Markdown.

6. Run and verify locally#

sh
hugo server --disableFastRender

Open the URL printed by Hugo. Check the sidebar, search, language and appearance menus, code colours and keyboard focus. A production build is hugo --minify.

Next steps#

  1. Read Configuration and set site identity and integrations.
  2. Follow the Content authoring guide.
  3. Review the feature map.
  4. Adopt the maintenance workflow before the first release.
Building without Node

Set params.build.tailwind = false to serve the theme’s tokens and component CSS without Tailwind utilities. Site-authored Tailwind utility classes will then not be generated.

Site-wide configuration

Configure Hugo, outputs, Markdown, menus, languages and theme parameters.

Configure the theme in layers. Start with Hugo’s site configuration, add theme parameters for presentation and integrations, then use front matter for page-level choices. Do not edit theme templates for settings that already have a parameter.

Which configuration files exist?#

FilePurposeMaintained by
hugo.tomlSite URL, module, languages, menus, outputs, Markdown and theme parametersSite owner
go.modHugo Module dependency and selected theme versionSite owner
package.json / lockfileTailwind CLI and browser-test toolsSite owner
data/cdn.yamlExact KaTeX, Mermaid and asciinema runtime versionsTheme maintainer
i18n/*.tomlTranslated interface labelsTheme maintainer or translator
Page front matterTitle, order, description and per-page capabilitiesContent author

The example at src/content/hugo.toml is executable test material. The copyable equivalent is included below so a site creator does not need to browse the theme repository.

Minimum site configuration#

hugo.toml
baseURL = "https://docs.example.com/"
title = "Example documentation"
defaultContentLanguage = "en"
defaultContentLanguageInSubdir = false
enableRobotsTXT = true

[module]
  [[module.imports]]
    path = "github.com/projectious-work/brand-theme-hugo-vanilla"

[build.buildStats]
  enable = true

baseURL must include any deployment prefix. The theme resolves internal links through Hugo so /project-name/ deployments work correctly.

Complete copyable configuration#

Paste this into the site-root hugo.toml created by hugo new site, then replace the example identity and repository values. Comments mark optional settings.

hugo.toml
baseURL = "https://docs.example.com/" # Canonical URL, including any path prefix.
title = "Example documentation"       # Site name used in metadata and the header.
defaultContentLanguage = "en"         # English is served when no language is specified.
defaultContentLanguageInSubdir = false # Keep the default language at the site root.
enableRobotsTXT = true                 # Ask Hugo to generate the theme's robots.txt.

[module] # Install the theme as a versioned Hugo Module.
  [[module.imports]]
    path = "github.com/projectious-work/brand-theme-hugo-vanilla" # Theme module.

[build.buildStats] # Let Tailwind discover classes emitted by Hugo templates.
  enable = true

[outputFormats.SearchIndex] # Local full-text search data at /index.json.
  mediaType = "application/json" # JSON response type.
  baseName = "index"              # Output filename without extension.
  isPlainText = true              # Do not wrap the generated JSON in HTML.
  notAlternative = true           # Keep this out of alternate-format metadata.

[outputFormats.Print] # Printable aggregate for each section.
  mediaType = "text/html" # Browser-renderable output.
  baseName = "print"      # Produces print.html.
  isHTML = true            # Enable Hugo's HTML processing.
  notAlternative = true    # Do not advertise it as an alternate page.
  permalinkable = true     # Give the output a stable URL.

[outputFormats.LLMS] # Machine-readable discovery document at /llms.txt.
  mediaType = "text/plain" # Plain UTF-8 text.
  baseName = "llms"        # Output filename without extension.
  isPlainText = true        # Do not apply an HTML wrapper.
  notAlternative = true    # Keep it out of alternate-format metadata.

[outputFormats.Markdown] # Clean Markdown representation of pages and sections.
  mediaType = "text/markdown" # Standard Markdown response type.
  isPlainText = true           # Do not apply an HTML wrapper.
  permalinkable = true         # Give every representation a stable URL.

[outputs] # Select the formats generated for each Hugo page kind.
  home = ["HTML", "RSS", "SearchIndex", "LLMS"] # Site-level discovery outputs.
  section = ["HTML", "RSS", "Print", "Markdown"] # Section aggregates and copies.
  page = ["HTML", "Markdown"] # Reader page plus clean Markdown.

[markup.goldmark.renderer] # Markdown-to-HTML safety policy.
  unsafe = false # Reject raw HTML from content files.
[markup.goldmark.extensions.passthrough] # Preserve formulas for KaTeX.
  enable = true
[markup.goldmark.extensions.passthrough.delimiters] # Accepted LaTeX delimiters.
  block = [["\\[", "\\]"], ["$$", "$$"]] # Display formulas.
  inline = [["\\(", "\\)"]]              # Formulas inside prose.
[markup.highlight] # Hugo Chroma syntax highlighting.
  noClasses = false # Emit semantic classes so the theme controls colours.
  guessSyntax = true # Highlight an unlabelled fence when detection succeeds.
[markup.tableOfContents] # Headings used by the right-hand navigation.
  startLevel = 2 # Begin with H2.
  endLevel = 3   # Include H3, but not deeper headings.

[[menus.main]] # Add a link to the primary header navigation.
  name = "Documentation" # Reader-facing label.
  pageRef = "/docs"       # Hugo page reference; checked during the build.
  weight = 10              # Lower weights appear first.

[params] # Public theme settings; defaults are documented below.
  description = "Documentation for the example product." # Site metadata summary.
  github = "https://github.com/example/example-docs" # Repository header link.
  editURL = "https://github.com/example/example-docs/edit/main/content/" # Edit base URL.
  version = "v1.0"             # Current label in the version menu.
  codeTheme = "adaptive"       # Adapt code panels to light and dark modes.
  sidebarSections = ["docs"]   # Top-level sections that receive the docs rail.
  sidebarOpenDepth = 1          # Initially expanded sidebar levels.

  [[params.versions]] # One published documentation version.
    label = "v1.0"   # Menu label.
    url = "/"        # Deployment-relative root for this version.
    note = "latest"  # Optional status annotation.

Required output formats#

HTML works without custom outputs. The integrated search, printing, Markdown copy and llms.txt features require the [outputFormats] and [outputs] blocks from the example configuration.

FormatWhat the theme producesWhy enable it
SearchIndex/index.json with pages and H2/H3 headingsHeader search and command palette
Printprint.html for an entire sectionPrintable handbook/PDF workflow
Markdownindex.md beside each pageCopy/View Markdown actions and agent consumption
LLMS/llms.txt with language-aware page linksA compact discovery file for LLM tools
RSSXML feeds styled by feed.xslRelease-note subscriptions

Add SearchIndex and LLMS to outputs.home; add Print and Markdown to sections; add Markdown to pages. The example configuration shows the exact media types and filenames.

Markdown configuration#

Copy the example [markup] block to enable:

  • Goldmark passthrough delimiters for KaTeX;
  • class-based Chroma syntax highlighting;
  • H2/H3 tables of contents; and
  • safe Markdown rendering with raw HTML disabled.

Turn markup.goldmark.renderer.unsafe on only when your own trusted content needs raw HTML. The theme itself does not require it.

Theme parameters#

Place these below [params] in hugo.toml:

The table is the complete public parameter set. Scalar parameters are assigned below [params]; tables and arrays use the TOML shapes shown in their detailed pages.

ParameterType and defaultConfigure whenDetails
descriptionstring, emptySetting site metadataHeader and navigation
githubURL, unsetShowing a repository header actionHeader and navigation
editURLURL or language map, unsetShowing Edit-this-page linksEditing and feedback
versionstring, unsetLabelling the current documentationVersioning
versionsarray, emptyLinking published documentation treesVersioning
versionProbeboolean, trueDisabling same-page version probing globallyVersioning
sidebarSectionsstring array, ["docs"]Putting the sidebar on other sectionsHeader and navigation
sidebarOpenDepthinteger, 1Changing initially expanded levelsHeader and navigation
codeThemeadaptive or dark, adaptiveForcing code and terminal panels darkTailwind and design tokens
darkSurfacenavy, unsetMaking navy the initial dark surfaceAccessibility
announcementtable, unsetShowing a dismissible noticeHeader and navigation
feedbackEndpointURL, unsetSending page votes to a serviceEditing and feedback
selfHostAssetsboolean, falseReplacing public CDN URLsDependencies
mathboolean, falseLoading KaTeX on every pageMathematics
searchboolean, trueDisabling search and its indexSearch
feedbackboolean, trueHiding the page-vote controlEditing and feedback
accessibilityMenuboolean, trueHiding reader preference controlsAccessibility
sidebarFilterboolean, trueHiding the sidebar filterSearch
commandPaletteboolean, trueDisabling the keyboard paletteHeader and navigation
build.tailwindboolean, trueBuilding without Node/TailwindTailwind and design tokens

Menus and languages#

Define primary links under menus.main; use pageRef for local pages. Define each language under [languages.<code>] with label and weight. See Header and navigation and Internationalization for complete examples.

Edit and feedback integrations#

See Editing and feedback for repository URL mapping, the request payload, the browser’s one-vote-per-page behavior and the security requirements for an optional receiving service.

Notebook conversion#

Notebook conversion is an optional pre-build operation:

sh
python3 -m venv .venv
. .venv/bin/activate
pip install -r scripts/requirements.txt
./scripts/notebooks.sh

It writes shared Markdown below content/_notebooks/ and images below static/notebooks/. The shortcode checks a page resource first, then the shared path. Notebook basenames must be unique.

Search engines and llms.txt#

robots.txt gives compliant web crawlers crawl instructions. Set enableRobotsTXT = true; Hugo then asks the theme to publish /robots.txt. The theme allows canonical content while excluding generated search indexes and print variants that duplicate it. For example:

text
User-agent: *
Disallow: /search/
Disallow: /*/print.html
Sitemap: https://docs.example.com/sitemap.xml

llms.txt is a discovery document, not a crawler rule. Enable it by defining the LLMS output format and adding "LLMS" to outputs.home, as in the copyable configuration. It contains page titles, descriptions and links to clean Markdown representations, without repeated header, sidebar and footer controls:

text
# Example documentation
## Documentation
- [Getting started](https://docs.example.com/docs/getting-started/index.md): Create a site.

Neither file is access control. Private information must not be published; use authentication at the hosting layer when content requires access restrictions.

Validate configuration changes#

Run ./scripts/verify.sh. It builds twice, compares artifacts, rejects project-base URL escapes and runs desktop/mobile browser tests. A configuration change is not complete until that command passes without warnings.

Versioned documentation

Publish separate documentation builds and connect them with the version menu.

Each documentation version is a separate Hugo build. Publish the newest release at the stable site root and older builds below prefixes such as /v0.2/. Never add a version-menu entry until that URL exists.

Add an archived version step by step#

  1. Check out the source tag for the older documentation, for example v0.2.0.

  2. Build that checkout with a base URL ending in /v0.2/:

    sh
    hugo --baseURL https://docs.example.com/v0.2/ --destination public/v0.2
  3. Publish the entire generated public/v0.2/ tree. It contains that release’s index.html, section/page directories, CSS, JavaScript, fonts, search index and other static assets—not Markdown source files copied by hand.

  4. Verify https://docs.example.com/v0.2/ and representative child pages.

  5. Return to the current source and add the selector entry shown below.

  6. Deploy current documentation without deleting the already-published v0.2/ directory.

toml
[params]
  version = "v0.3"

  [[params.versions]]
    label = "v0.3"
    url = "/"
    note = "latest"

  [[params.versions]]
    label = "v0.2"
    url = "v0.2/"

The menu normally appends the current page path so readers stay on the same topic. Set params.versionProbe = false, or probe = false on one entry, when versions have different structures. Older builds display a banner linking to the first configured version.

The repository’s release script deploys the current release only. A consumer that retains multiple versions needs a deployment step that copies the new root while preserving each archived generated tree.

Page configuration (front matter)

Control navigation, cards, tables of contents and optional page capabilities.

Front matter is the TOML block between +++ delimiters at the beginning of a Markdown file. These values affect only that page or section.

toml
+++
title = "API reference"       # Required page title.
linkTitle = "API"             # Optional shorter navigation label.
description = "HTTP API."     # Summary, metadata and search excerpt.
weight = 20                    # Navigation and previous/next order.
icon = "code"                 # Icon on a generated child-page card.
toc = true                     # Show the right-hand table of contents.
cards = true                   # Generate cards for child pages on a section.
hidden = false                 # Include this child in generated cards.
math = false                   # Load KaTeX only when this page needs it.
private = false                # Include the page in search and sitemap.
cover = "/img/api.png"         # Optional blog cover image.
coverAlt = "API response"      # Required alternative text for the cover.
+++

Complete key reference#

KeyType and defaultEffect
titlestring, requiredPage title and default navigation label
linkTitlestring, titleShorter navigation label
descriptionstring, emptyLede, SEO description and search excerpt
weightinteger, 0Sidebar, card and previous/next order
iconTabler name, page iconGenerated overview-card icon
tocboolean, trueShow or hide heading navigation
cardsboolean, trueGenerate cards for children of a section
hiddenboolean, falseExclude a child from generated cards
mathboolean, falseLoad KaTeX on this page
privateboolean, falseExclude from search and sitemap when true
coverpath, unsetBlog cover image
coverAltstring, emptyAlternative text for the cover image

Use quotes for strings, plain integers for weights, and true or false for booleans. The values shown in the type column are defaults, not placeholders.

Template authoring guide

Move from Markdown authoring to your first upgrade-safe Hugo shortcode and layout, step by step.

This guide starts with the knowledge needed to create ordinary Markdown pages. You do not need to know Go, HTML templating or Tailwind beforehand. By the end, you will have created a reusable status-panel component and will understand when and how to change a complete page layout.

1. Understand what changes when Markdown is not enough#

A Markdown file supplies content: headings, paragraphs, lists, images and front matter. A template supplies the HTML around that content. Hugo combines the two when it builds the site.

For example, this Markdown:

md
+++
title = "Service status"
+++

The service is available.

becomes a complete HTML page because the theme supplies the header, sidebar, main content area and footer. You only need to author a template when you want reusable presentation that Markdown and the theme’s existing shortcodes do not already provide.

Hugo has three relevant building blocks:

Building blockWhat it doesWho invokes it
ShortcodeEmbeds a reusable component inside MarkdownContent author
PartialReuses HTML inside another templateTemplate author
LayoutDefines the structure of a complete page or sectionHugo

Start with a shortcode for a component used in content. It is the smallest and safest first customization. Do not copy a complete theme layout merely to add a box to one page.

2. Know which project you are editing#

The theme is a dependency of your Hugo site. Your site should have a structure similar to this:

text
my-site/
├── assets/
├── content/
│   └── docs/
├── layouts/
├── hugo.toml
├── go.mod
└── package.json

All paths in this guide are relative to my-site/, the directory containing your site’s hugo.toml. Create assets/ or layouts/ if they do not exist. Do not edit the downloaded theme in Hugo’s module cache. Hugo checks your local site first, so a local file can add to or override the theme without changing the dependency.

Before changing templates, make sure the ordinary site still builds:

sh
hugo

If this command fails, first complete the from-scratch installation.

3. Learn the small amount of template syntax you need#

Hugo templates are HTML with instructions between {{ and }}. These are called template actions. The first example uses only five ideas:

SyntaxMeaning
{{ .Get "title" }}Read the shortcode argument named title
{{ .Inner }}Read the Markdown between the opening and closing shortcode tags
{{ $name := value }}Store a value in a local variable
{{ default "info" value }}Use "info" when no value was supplied
{{ index $map $key }}Read the value for $key from a map

A leading or trailing hyphen, as in {{- or -}}, only trims surrounding whitespace in the generated HTML. It does not change the value.

4. Create your first shortcode without styling#

Create layouts/shortcodes/status-panel.html in your site:

layouts/shortcodes/status-panel.html
<aside>
  <h2>{{ .Get "title" }}</h2>
  <div>{{ .Inner | .Page.RenderString }}</div>
</aside>

This is a paired shortcode because it accepts content between an opening and a closing tag. .Page.RenderString tells Hugo to render Markdown such as emphasis and links inside that content.

Use the shortcode in any Markdown page:

md
{{< status-panel title="Release status" >}}
The **documentation build** passed all checks.
{{< /status-panel >}}

Run the development server and open the page containing the shortcode:

sh
hugo server

At this stage the component is intentionally plain. Confirm that its heading and bold text render before adding styling. If Hugo reports failed to extract shortcode, check the filename, paired closing tag and quotation marks.

5. Enable the theme’s Tailwind integration#

Tailwind turns utility names in class="..." attributes into CSS. The theme already owns the stylesheet and exposes a documented set of semantic utilities. Your site must let Hugo record the class names found in your local templates.

In the site-root hugo.toml, add or merge this setting:

toml
[build.buildStats]
  enable = true

Do not create a second [build.buildStats] table if one already exists; add enable = true to the existing table.

If you build the theme CSS in the consuming project, install the same major Tailwind CLI version and commit package.json and the generated lockfile:

sh
npm install --save-dev @tailwindcss/cli@^4.1.0

The theme reads Hugo’s generated hugo_stats.json. This is how utility classes used by local templates become visible to Tailwind. Run the repository’s normal build command after adding a class; hugo server alone may not rebuild CSS in a custom build setup.

6. Style the shortcode with theme utilities#

Replace the simple shortcode with this styled version:

layouts/shortcodes/status-panel.html
{{- $kind := .Get "kind" | default "info" -}}
{{- $classes := dict
  "info" "border-default bg-subtle text-hi"
  "important" "border-accent bg-surface text-hi"
-}}
<aside class="my-6 rounded-lg border p-6 {{ index $classes $kind }}">
  <h2 class="font-display text-accent">{{ .Get "title" }}</h2>
  <div class="font-sans text-hi">{{ .Inner | .Page.RenderString }}</div>
</aside>

Use it from Markdown:

md
{{< status-panel title="Release status" kind="important" >}}
The documentation build passed all checks.
{{< /status-panel >}}

Read it from top to bottom:

  1. $kind reads an optional argument and falls back to info.
  2. $classes maps the two supported values to complete class strings.
  3. index selects one class string for the <aside> element.
  4. .Get "title" writes the heading supplied by the author.
  5. .Inner | .Page.RenderString renders the enclosed Markdown.

Use complete, literal class names in the map. Do not construct a class such as bg-{{ .Get "colour" }}. Tailwind cannot reliably discover dynamically assembled names, and allowing arbitrary presentation values makes the component harder to maintain.

7. Choose theme-safe colours, spacing and typography#

Use the documented utilities—font-display, font-sans, font-mono, bg-page, bg-surface, bg-subtle, bg-terminal, text-hi, text-lo, text-accent, border-default and border-accent. For custom CSS, prefer semantic variables such as --color-surface, --color-text-primary, --color-border, --space-5 and --radius-lg.

See Tailwind and design tokens for the complete mapped surface. Avoid depending directly on internal component selectors or raw palette steps when a semantic token exists.

The semantic names adapt to light mode, dark mode and accessibility settings. A hard-coded class such as bg-black may look correct in one mode but remain black in all modes. Test semantic utilities before adding custom CSS.

8. Add an icon only when it conveys useful information#

Place an additional Tabler outline SVG at assets/icons/<name>.svg; Hugo’s merged asset filesystem makes it available to the theme partial and shortcode:

go-html-template
{{ partial "icon.html" (dict "name" "chart-bar" "class" "ico--lg" "label" "Usage chart") }}

Decorative icons omit label and become aria-hidden. Icon-only controls need an accessible name on the control itself. See Icons and Tabler.

If the icon is purely decorative, omit label. If it communicates meaning that is not already present in nearby text, provide a short label. Do not use an icon as the only way to communicate status or severity.

9. Decide whether you really need a page layout#

The shortcode above adds a component inside .Content; it does not move the sidebar, breadcrumbs or page title. A layout is appropriate only when the HTML structure of a whole page type must change.

Before overriding one, identify the exact upstream file:

  1. Find the layout that renders the page in the theme’s layouts/ directory.
  2. Copy that one file into the identical path below your site’s layouts/.
  3. Make the smallest possible change.
  4. Keep a note of the theme version from which it was copied.

Copy the relevant theme layout into the same path in the consuming site, for example layouts/docs/single.html. Preserve the base template contract:

layouts/docs/single.html
{{ define "main" }}
<div class="docs">
  {{ partial "sidebar.html" . }}
  <main id="main" class="docs__main">
    {{ partial "breadcrumbs.html" . }}
    <article class="prose">
      <h1>{{ .Title }}</h1>
      {{ .Content }}
    </article>
  </main>
</div>
{{ end }}

Start from the exact version of the upstream file you consume. Record the source tag in a comment or commit message so future maintainers can compare it.

The line {{ define "main" }} fills a named area supplied by the theme’s base template. The dot passed to a partial, as in {{ partial "sidebar.html" . }}, is the current page context. .Title comes from front matter and .Content is the rendered Markdown body.

If all you need is a component inside Markdown, stop at the shortcode. A copied layout follows the theme less automatically and therefore needs upgrade review.

10. Diagnose common first-time problems#

SymptomLikely causeWhat to check
template for shortcode ... not foundWrong path or filenameUse layouts/shortcodes/<name>.html in the site root
The shortcode text appears literallyEscaped example syntax was copiedUse real {{< ... >}} delimiters without the documentation escapes
.Inner is unavailableClosing tag is missingAdd {{< /status-panel >}}
New classes have no effectCSS was not rebuilt or the class is dynamicEnable build stats, use literal classes and run the full build
Colours work only in one modeRaw colour values were usedChoose semantic theme utilities or variables
A copied layout loses theme featuresThe base-template contract changedStart from the exact layout in your installed theme version

Hugo error messages normally include the template path and line number. Start at the first reported error; later errors can be consequences of the first one.

11. Verify behavior and accessibility#

Run a production build and the consuming site’s tests:

sh
hugo --minify

Check light and both dark surfaces, 200% text size, keyboard focus, reduced motion, mobile layout, print output and every supported language. Confirm that links keep the deployment base path and that the generated CSS contains the new utilities.

For the status-panel, verify at least:

  • the default form and kind="important";
  • Markdown links and emphasis inside the panel;
  • light and dark colour modes;
  • keyboard navigation when the content contains a link;
  • narrow mobile width and 200% browser zoom; and
  • a production build with no template warning.

12. Maintain your customization#

When upgrading the theme:

  1. Read release notes for template and token changes.
  2. Diff every local override against the same upstream path in the new tag.
  3. Remove an override when upstream now provides the required extension point.
  4. Re-run visual and accessibility tests before committing go.mod and go.sum.

Local overrides are intentionally your responsibility; keeping them small and tested is the best protection against upgrade drift.

The safest progression is therefore: Markdown first, an existing theme shortcode second, a small local shortcode third, and a complete layout override only when the page structure truly has to change.

Tokens and public API

Inspect the stable semantic CSS tokens and Tailwind namespaces generated from the theme source.

The tables below are generated from the theme’s CSS during the Hugo build. Use semantic tokens and documented Tailwind namespaces in site-owned templates; palette steps, component selectors and internal behavior attributes may change.

The root API.md defines the complete compatibility contract. For practical authoring examples, see Tailwind and design tokens.

Terminal recordings

Record and embed copyable terminal sessions with asciinema.

asciinema records terminal text and timing data rather than video. Recordings stay sharp, remain copyable, and are usually much smaller than screen video.

Install the recorder, run asciinema rec theme-tour.cast, and put the resulting file in a page bundle or under static/casts/.

md
{{< asciinema src="/casts/theme-tour.cast" rows="8" cols="80" idleTimeLimit="1.5" >}}

Autoplay is off by default. The player version is pinned in data/cdn.yaml; see Dependencies for CDN and self-hosting options.

Tailwind and design tokens

Use the theme's Tailwind integration and stable projectious.work design tokens.

Content authors normally use Markdown and shortcodes; they do not need Tailwind. Template developers may use Tailwind utilities in site-owned layouts and shortcodes. Hugo’s build statistics tell Tailwind which classes were actually emitted, so [build.buildStats] enable = true is required.

Available Tailwind namespaces#

The theme maps brand tokens in assets/css/theme-layer.css:

KindUtilitiesMeaning
Fontsfont-display, font-sans, font-monoPlus Jakarta Sans, Source Sans 3, IBM Plex Mono
Coloursbg-page, bg-surface, bg-subtle, bg-terminalPage, raised, subtle and terminal surfaces
Texttext-hi, text-lo, text-accentPrimary, supporting and accent text
Bordersborder-default, border-accentStandard and accent boundaries
Radiusrounded-sm, rounded-md, rounded-lg, rounded-xl3, 6, 9 and 13 pixels
Spacingstandard Tailwind multiples of 4pxp-1 = 4px, gap-6 = 24px
Breakpointssm, md, lg, xl640, 768, 1024 and 1280 pixels
html
<aside class="rounded-lg border border-default bg-surface p-6 text-hi">
  <h2 class="font-display text-accent">Release status</h2>
  <p class="font-sans text-lo">All documentation checks passed.</p>
</aside>

CSS token reference#

Use CSS custom properties when a utility is not appropriate. The complete source of truth is assets/css/brand-tokens.css; it defines the 12-step Midnight, Orange and Slate scales, semantic success/warning/danger/info colours, typography, spacing, radii, shadows, motion, breakpoints, layout measurements, terminal ANSI slots and light/dark syntax roles. Prefer semantic tokens such as --color-text-primary, --color-surface, --color-border, --space-5, --radius-lg and --type-body-size over raw palette steps.

The implementation follows the projectious.work brand design system. Set params.build.tailwind = false only when site templates contain no Tailwind utilities; the theme’s component CSS and tokens still render.

Shortcodes

Every component the theme ships, with the Markdown that produces it.

Each section shows the rendered component, then the exact Markdown. Nothing here needs raw HTML — the theme ships with unsafe = false.

Markdown first

Prose, lists, links, images, tables and code fences are ordinary Markdown. A fenced block already gets a filename bar, language label and copy button; an image already becomes a captioned, lazy-loaded figure that opens in a lightbox. Reach for a shortcode only where a component needs structured children.

Shortcode syntax: paired and unpaired#

Hugo decides which form to use from the template. A shortcode whose template reads .Inner requires a closing tag; one that has no inner content must not have one or use an XML-style trailing slash.

Unpaired: card, file, icon, badge, button, term, image, notebook, asciinema.

Paired: callout, cards, tabs, tab, steps, step, details, filetree, folder, terminal, mermaid.

{{< badge "v0.3" >}}              <!-- unpaired -->
{{< details title="Why?" >}}      <!-- paired -->
It wraps content, so it closes.
{{< /details >}}

Keep shortcode-syntax examples in top-level fenced or indented blocks. Content inside a paired shortcode is parsed again, including escaped examples.

Callouts#

Information a reader needs but did not ask for.

Note

Context that supports the main line.

The documentation build completed successfully.

Careful

Preview configuration changes before publishing them.

Failed

The referenced page was not found.

Sentence case everywhere. The brand name stays lowercase.

md
{{< callout type="warning" title="Careful" >}}
Preview configuration changes before publishing them.
{{< /callout >}}

type takes info, note, success, warning, error or important.

Cards#

A section’s overview cards are generated from its child pages’ front matter — title, description, icon, weight — so you never hand-maintain a card list that drifts as pages come and go. Set cards = false in a section’s front matter to suppress the grid, or hidden = true on a child to leave it out.

Write cards by hand only for a set that is not a page list:

Authoring

Write pages with Markdown and focused shortcodes.

Configuration

Choose navigation, outputs and optional features.

md
{{< cards cols="2" >}}
  {{< card title="Authoring" subtitle="Write pages with Markdown." link="/docs/guides/" icon="file-code" >}}
  {{< card title="Configuration" subtitle="Choose site options." link="/docs/configuration/" icon="list" >}}
{{< /cards >}}

cols is 2, 3 (default) or 4, so an incomplete last row keeps its width. card also takes image and alt for an image card. subtitle is rendered as Markdown, so links and code spans work inside it.

Tabs#

sh
npm install
sh
pnpm install
sh
go mod download
md
{{< tabs items="npm, pnpm, go" >}}
  {{< tab >}}First panel.{{< /tab >}}
  {{< tab >}}Second panel.{{< /tab >}}
  {{< tab >}}Third panel.{{< /tab >}}
{{< /tabs >}}

Panel order follows tab order. Arrow keys move between tabs.

Steps#

Install Hugo

Use the minimum version listed in the theme’s README.

Configure the module

Add the theme import and required output formats.

Run

Start the local server and review the generated pages.

A step is not limited to plain text. Its body is Markdown and may contain lists, code, images, callouts, or nested shortcodes such as cards. Use % delimiters for step so Hugo renders that mixed body before the step template receives it.

md
{{< steps >}}
  {{% step title="Install Hugo" %}}
Use the supported version.
  {{% /step %}}
  {{% step title="Configure the module" %}}
Add the theme import.
  {{% /step %}}
{{< /steps >}}

Numbering is CSS counters — steps renumber themselves when you reorder them.

Collapsible details#

What does the search index contain?

Title, description, breadcrumb, tags, every H2 and H3 heading, and the first 2000 characters of plain text per page — generated by the SearchIndex output format.

md
{{< details title="What does the search index contain?" >}}
Title, description, breadcrumb, tags and headings.
{{< /details >}}

Add open="true" to start expanded.

File tree#

  • content
    • _index.md landing
    • docs
      • getting-started.md
      • configuration.md
    • blog
      • release-v0-3-0.md
md
{{< filetree >}}
  {{< folder name="content" >}}
    {{< file name="_index.md" note="landing" >}}
    {{< folder name="docs" >}}
      {{< file name="getting-started.md" >}}
    {{< /folder >}}
    {{< folder name="blog" closed="true" >}}
      {{< file name="release-v0-3-0.md" >}}
    {{< /folder >}}
  {{< /folder >}}
{{< /filetree >}}

folder takes closed="true"; file takes note for a trailing chip and icon to override the glyph.

Icons#

md
{{< icon "brand-github" >}}
{{< icon name="printer" class="ico--lg" label="Print" >}}

Any file in assets/icons/ works by name. label gives an icon a text alternative; without it the glyph is aria-hidden.

Badges#

v0.3 latest

md
{{< badge "v0.3" >}}
{{< badge label="latest" variant="accent" >}}

Buttons#

Read the docs Search

md
{{< button label="Read the docs" href="/docs/" >}}
{{< button label="Search" href="/search/" variant="secondary" icon="search" >}}

variant is primary (default) or secondary.

Terminal#

deploy
$ hugo server --disableFastRender
Watching for changes in content and layouts
Built in 284 ms
Web Server is available at http://localhost:1313/
md
{{< terminal title="deploy" >}}
$ hugo server --disableFastRender
Watching for changes in content and layouts
Built in 284 ms
Web Server is available at http://localhost:1313/
{{< /terminal >}}

For a plain code listing use a fenced block — the filename bar, language label and copy button come free, and fence options work:

```yaml {filename="navigation.yaml", linenos=table, hl_lines="2-3"}
title: Documentation
weight: 10
icon: book
```

Terminology#

A module installs the theme. A page bundle keeps page resources together, while a shortcode adds structured markup.

md
A {{< term "module" >}} installs the theme.
{{< term key="page-bundle" label="Page bundles" >}} overrides the displayed text.

Definitions live in data/glossary.yaml, so a wording change is one edit.

Links#

Ordinary Markdown links work, and internal ones resolve against the site’s base path — so a project deployment such as GitHub Pages keeps its prefix:

md
[Configuration](/docs/configuration/)

Prefer Hugo-aware references so moving a target fails the build instead of silently publishing a broken link:

md
[Configuration](configuration/_index.md)
[Guides](<guides/_index.md>)
[Output formats](configuration/site-wide.md#output-formats)
[Linking guide](guides/_index.md#link-within-and-between-pages)

External links get target="_blank", rel="noopener noreferrer" and an icon automatically.

Images#

Markdown images become figures automatically — the title becomes the caption:

md
![Documentation navigation](navigation.png "Generated navigation on a documentation page")

Prefer a page bundle: put navigation.png next to the page’s index.md and reference it by name. Hugo then knows its dimensions, so the figure carries intrinsic width and height and the page does not shift as images load. A sibling navigation-dark.png is picked up and swapped by colour mode. To be explicit, or to caption with Markdown:

md
{{< image src="/img/navigation.png" src-dark="/img/navigation-dark.png"
       alt="Documentation navigation" caption="Navigation in *both* modes" >}}

Every image in prose opens in a lightbox on click or Enter.

Diagrams#

flowchart LR
  A[Markdown] --> B[Hugo]
  B --> C[HTML]
  B --> D[Search index]
  B --> E[Print output]
md
{{< mermaid >}}
flowchart LR
  A[Markdown] --> B[Hugo]
{{< /mermaid >}}

A ```mermaid fence renders the same way. Mermaid picks up the brand palette and the current colour mode.

Math#

Set math = true in front matter, then write LaTeX inline — \( E = mc^2 \) — or as a block:

$$ \text{cost}(n) = c_{\text{fixed}} + n \cdot c_{\text{run}} $$
md
Inline: \\( E = mc^2 \\) or $E = mc^2$

$$
\text{cost}(n) = c_{\text{fixed}} + n \cdot c_{\text{run}}
$$

Both $…$/$$…$$ and \(…\)/\[…\] are recognised.

Terminal recordings#

md
{{< asciinema src="/casts/deploy.cast" rows="18" idleTimeLimit="1.5" >}}

Record with asciinema rec deploy.cast and keep the file in the page bundle, or under static/casts/. cols, rows, speed, idleTimeLimit, autoplay and loop are all supported — autoplay is off by default, per the brand rule on motion.

Notebooks#

md
{{< notebook "analysis" >}}

Run scripts/notebooks.sh first. The shortcode looks for a page resource named analysis.md in the page bundle, then content/_notebooks/analysis.md — both exact, so duplicate basenames are an error rather than a coin flip.

Tags

Use Hugo taxonomies for related-content and release-note metadata.

Tags are ordinary Hugo taxonomy terms. Add them to front matter:

toml
tags = ["release", "accessibility"]

The theme renders tags in page metadata and generates Hugo’s term and taxonomy pages. Tags are also included in the search index. The example site deliberately does not place a Tags link in the primary header; link to a curated term from your content when it helps readers, or add /tags/ to your own menu.

Configure a different taxonomy through Hugo’s [taxonomies] table and provide the corresponding templates if its presentation differs from tags.

Developer guide

Adapt the theme, swap bundled assets, run tests and contribute changes safely.

Theme consumers should override files through Hugo Modules or their project layout instead of editing a cached module. Contributors to this repository work below src/; release and deployment scripts intentionally remain outside that tree.

Repository structure#

text
src/assets/       CSS, JavaScript and inline SVG icons
src/layouts/      Hugo templates, partials, render hooks and shortcodes
src/i18n/         interface translations
src/data/         glossary and pinned runtime metadata
src/static/       fonts, logos and licences
src/content/      executable multilingual example site
scripts/          local build, verification, release and deployment commands
tests/browser/    Playwright behavior and visual baselines

Override or adapt a template#

Hugo’s lookup order gives the consuming site’s layouts/ precedence over module templates. Copy only the partial or template you need to change and preserve its public parameters. This limits the merge work required during upgrades.

CSS variables in brand-tokens.css are the stable styling surface. Put site-level overrides in a later stylesheet rather than changing component selectors whenever possible.

Icons and Tabler#

The theme currently bundles this small offline fallback set:

accessible, alert-circle, alert-triangle, arrow-left, arrow-right, book, brand-github, check, chevron-down, chevron-left, chevron-right, chevron-up, circle-check, clock, copy, device-desktop, external-link, file, file-code, folder, info-circle, language, list, menu-2, moon, pencil, player-play, printer, search, star, sun, tag, thumb-down, thumb-up, versions and x.

The fallback source files are in src/assets/icons/fallback/. Use a name with {{< icon "search" >}} or front-matter icon = "search". The resolver checks a site override, the mounted Tabler outline library and then the bundled fallback. Install the pinned library and mount it in hugo.toml:

sh
npm install --save-exact @tabler/icons@3.31.0
toml
[[module.mounts]]
  source = "node_modules/@tabler/icons/icons/outline"
  target = "assets/tabler-icons/outline"

Every Tabler outline icon is then available by its filename without .svg. A site can still override or add a glyph at assets/icons/<name>.svg. See the root ICONS.md for resolution order, naming and MIT attribution.

This icon is resolved from the mounted full library rather than the fallback set:

md
{{< icon name="rocket" class="ico--lg" label="Rocket from Tabler Icons" >}}

Tailwind in site templates#

Content Markdown should not contain presentation classes. Site-owned layouts and shortcodes may use the theme’s Tailwind namespaces and CSS tokens. See Tailwind and design tokens for the complete mapped utility surface, token categories and a working component example.

Swap fonts or runtime assets#

Font faces live in src/static/fonts/; declarations live in fonts.css. Replace both files and licence documents together, then test layout at 200% text size.

KaTeX, Mermaid and asciinema pins live in src/data/cdn.yaml. For offline hosting, mirror the exact file structure below static/vendor/ and enable params.selfHostAssets. See Dependencies and SBOM.

Build and test#

sh
npm install
./scripts/build.sh
./scripts/verify.sh
./scripts/serve-watch.sh start

build.sh performs two Hugo passes because Tailwind consumes Hugo’s generated class inventory. verify.sh compares two output trees, validates base-path links and runs Playwright at desktop and mobile sizes.

When an intentional visual change fails a snapshot, inspect the actual and diff images first. Only then run npx playwright test --update-snapshots and review the new files.

Contribute#

Create a short-lived branch from main, use Conventional Commits, add tests for behavior changes, run verification locally and open a pull request referencing its work item. Do not add GitHub Actions: this repository’s release policy is explicitly local-only. See the root CONTRIBUTING.md for the complete workflow.

Search

How the generated search index, header search and command palette work.

Search is local and requires no hosted service. Hugo writes /index.json; the browser loads the bundled FlexSearch runtime only on pages where search is enabled. Each page contributes its title, description, breadcrumb, tags, content and H2/H3 headings, so a heading result links directly to its anchor.

Reader experience#

  • Type in the header field or press / to focus it.
  • Press Ctrl/Cmd+K for the command palette.
  • Filter the dedicated search page by top-level section.
  • Use arrow keys and Enter to select a result.

Configuration#

Copy the SearchIndex output format and add it to outputs.home, as shown in Configuration. Create content/search.md with layout = "search". Set params.search = false to remove the header field, index and search page integration.

Set private = true on a page to omit it from search and the sitemap. This is publication control, not access control: do not put secrets in a static site.

Maintenance and upgrades

Keep the theme, runtime pins, content and published documentation current.

Treat theme upgrades as reviewed application changes. They can affect templates, generated URLs, accessibility, search records and screenshots even when content is unchanged.

Upgrade a consuming site#

  1. Read the release notes and compare configuration examples.

  2. Update the pinned Hugo Module version:

    sh
    hugo mod get github.com/projectious-work/brand-theme-hugo-vanilla@vX.Y.Z
    hugo mod tidy
  3. Update locked Node dependencies with npm install when the release changes package.json.

  4. Compare local template overrides with their new upstream versions.

  5. Build with the production baseURL, run link and browser checks, and review visual changes in every supported language.

  6. Commit go.mod, go.sum, package lockfiles and required configuration changes together.

Check for a new release#

Copy scripts/check-theme-update.sh from the theme release into the consuming site’s scripts/ directory and make it executable. It reads the installed version from Hugo’s module graph and compares it with upstream SemVer tags:

sh
chmod +x scripts/check-theme-update.sh
./scripts/check-theme-update.sh

It is check-only by default and exits with status 10 when an update is available, which makes it suitable for a scheduled local task. After reading the release notes and committing or stashing site changes, apply the update explicitly:

sh
./scripts/check-theme-update.sh --update

The update mode runs hugo mod get and hugo mod tidy; it deliberately does not rewrite configuration, overwrite local templates or publish. Review the resulting go.mod/go.sum, compare configuration examples, then run the consuming site’s build, link, accessibility and visual tests.

Routine checks#

  • Run npm audit and review direct dependency updates.
  • Test the minimum and current supported Hugo versions.
  • Check external links and the Edit-this-page prefix.
  • Verify search, version and language menus after adding or moving pages.
  • Review translated pages whenever English structure changes.
  • Confirm CDN pins still exist and self-hosted mirrors match them.
  • Re-run accessibility checks after CSS, navigation or component changes.

Maintain versioned documentation#

Publish a version URL before adding it to params.versions. Keep canonical current documentation at the stable root. When removing an old version, remove its selector entry and configure redirects where links may remain in the wild.

Repository release chain#

This repository uses local scripts rather than GitHub workflows. From a clean, synchronized main after review:

sh
./scripts/release.sh vX.Y.Z

The script verifies deterministic output and browser baselines, packages the theme, creates the annotated tag and GitHub release, then deploys that tagged commit to GitHub Pages. Never deploy an unreviewed working tree.

Recovery and support#

Published tags and GitHub release archives are immutable recovery points. Report security issues through SECURITY.md; use GitHub issues for reproducible defects and discussions for usage questions when enabled.

Mathematics

Render inline and display mathematics with KaTeX.

KaTeX renders LaTeX notation. Set math = true in the page front matter first. Inline mathematics such as \( t_{build} < 1s \) stays in a sentence; display mathematics gets its own block:

$$ T_{publish} = T_{build} + T_{verify} + T_{deploy} $$
md
+++
math = true
+++

Inline: \\( t_{build} < 1s \\)

$$
T_{publish} = T_{build} + T_{verify} + T_{deploy}
$$

See Page configuration for the front-matter contract and Dependencies for the runtime pin.

Dependencies and SBOM

Understand build tools, browser runtimes, bundled assets, versions and licences.

The theme has no server runtime. Hugo produces static files. Dependencies fall into build-time tools, browser-loaded libraries and bundled assets.

Direct dependency inventory#

ComponentVersionDeliveryPurposeLicence
Hugo0.128.0 minimum; verified with 0.164.0Build toolStatic-site generation and asset pipelineApache-2.0
Go1.22 module declarationBuild toolHugo Module resolutionBSD-3-Clause
@tailwindcss/cli4.3.3 lockednpm development dependencyCompile utility CSS from Hugo build statisticsMIT
Playwright test1.62.1 lockednpm development dependencyBrowser behavior and visual regression testsApache-2.0
Tabler Icons3.31.0 lockednpm development dependency mounted into Hugo assetsComplete outline icon catalogueMIT
IBM Plex Mono font package5.3.0 lockednpm development source for bundled WOFF2 cutsCode and syntax typographySIL OFL 1.1
FlexSearch0.8.143Bundled browser assetLocal full-text searchApache-2.0
KaTeX0.18.4Pinned CDN or self-hostedMathematics renderingMIT
Mermaid11.16.1Pinned CDN or self-hostedDiagram renderingMIT
asciinema-player3.17.0Pinned CDN or self-hostedTerminal recording playbackApache-2.0
nbconvert7.16.6Optional pinned Python toolConvert Jupyter notebooks to MarkdownBSD-3-Clause
Bundled fallback icons38 theme glyphsBundled assetsOffline interface fallbackMIT

Exact browser-runtime URLs are maintained in src/data/cdn.yaml; exact npm transitives and integrity values are in package-lock.json; Python conversion pins are in scripts/requirements.txt.

Bundled fonts and icons#

Plus Jakarta Sans, Source Sans 3 and IBM Plex Mono are bundled as WOFF2 under the SIL Open Font License 1.1. IBM Plex Mono includes normal and italic cuts at 400, 500, 600 and 700 so syntax roles use real faces rather than browser-synthesized weight or oblique. Licence texts ship under src/static/fonts/licenses/. The theme’s fallback SVG set follows Tabler geometry. The exact Tabler dependency is mounted from node_modules during the example build; consuming sites may use the same mount or rely on the fallback set. See Icons and Tabler. FlexSearch’s licence ships under src/static/licenses/flexsearch/.

SBOM scope and maintenance#

This page is the human-readable software bill of materials for v0.3.x. Before each release, maintainers must compare it with package-lock.json, requirements.txt, data/cdn.yaml, bundled asset directories and Hugo/Go declarations. Generated CycloneDX or SPDX output may supplement this page, but must not replace checked-in licence files or the exact lockfiles.

Run npm audit --json for the Node graph. CDN and bundled assets require separate release-note and advisory review because npm audit cannot see them.

Jupyter notebooks

Convert and publish reproducible notebook output.

Jupyter notebooks combine prose, executable code, and output in .ipynb JSON files. The theme converts them before Hugo builds, so the published page contains ordinary Markdown and images rather than executable code.

Build-time notebook output#

This preview represents Markdown produced by the pinned nbconvert workflow. A real notebook can include narrative cells, highlighted input and tabular output.

analysis.ipynb · cell 1
pages = 98
languages = ["English", "Deutsch", "Français"]
pages_per_language = pages / len(languages)
pages_per_language
output
32.67
LanguagePublished
Englishyes
Deutschyes
Françaisyes

Create an isolated conversion environment and convert every notebook:

sh
python3 -m venv .venv
. .venv/bin/activate
pip install -r scripts/requirements.txt
./scripts/notebooks.sh

The pinned nbconvert environment makes conversion reproducible and keeps the tools outside the system Python installation. Embed the converted result with {{< notebook "theme-demo" >}}. The shortcode accepts a converted file; it cannot execute or contain an inline notebook. See Notebook conversion for lookup paths.

Internationalization

Configure translated content, language navigation, metadata and RTL layout.

Use Hugo’s multilingual configuration and place translated content below language directories. The example site contains complete English, German and French trees.

toml
defaultContentLanguage = "en"

[languages.en]
  label = "English"
  weight = 1
[languages.de]
  label = "Deutsch"
  weight = 2
[languages.fr]
  label = "Français"
  weight = 3

The language menu links to the current page’s translation when available and falls back to that language’s home page. Search indexes, edit links, RSS, canonical URLs, sitemaps, hreflang and llms.txt remain language-aware.

Translate interface strings in i18n/<lang>.toml. For right-to-left languages set languagedirection = "rtl"; structural rails, chevrons and menus mirror.

Keep filenames and translation keys stable across languages. Hugo then associates translations without custom template logic.

Header and navigation

Configure primary links, repository actions, search and header controls.

The header contains the brand link, primary menu, search, version and language menus, accessibility and colour controls, and an optional repository link. On wide screens it aligns with the documentation shell; on narrow screens documentation navigation moves into a drawer.

Primary links#

Use Hugo menus in hugo.toml:

toml
[[menus.main]]
  name = "Documentation"
  pageRef = "/docs"
  weight = 10

[[menus.main]]
  name = "Blog"
  pageRef = "/blog"
  weight = 20

Prefer pageRef for site pages and url for external destinations. Ordering is by weight.

Header icons and links#

params.github adds the bundled GitHub icon with an accessible label. For another service, add an icon SVG below assets/icons/ and extend partials/header.html with an .iconbtn link using partials/icon.html. Keep the link at least 44 by 44 CSS pixels and provide an aria-label.

See Bundled icons for the complete icon list.

Editing and feedback

Connect pages to their source files and optionally collect useful reader votes.

Edit this page#

params.editURL is the repository URL prefix containing the site’s Markdown files. For a page at content/docs/install.md, this configuration creates a link to https://github.com/org/repo/edit/main/content/docs/install.md:

toml
[params]
  editURL = "https://github.com/org/repo/edit/main/content/"

Use a language map when each language has its own content root:

toml
[params.editURL]
  default = "https://github.com/org/repo/edit/main/content/en/"
  de = "https://github.com/org/repo/edit/main/content/de/"
  fr = "https://github.com/org/repo/edit/main/content/fr/"

The control is omitted when editURL is unset or the page has no source file.

Page feedback#

The Yes/No control works locally by remembering one vote per page in browser storage. Set params.feedback = false to hide it. To collect votes centrally, set params.feedbackEndpoint to an HTTPS endpoint. The browser sends:

json
{"path":"/docs/install/","value":"up","title":"Install","lang":"en"}

The browser avoids repeated submissions from the same browser—at most one network request per page per hour and ten per tab session. This is only interface behavior: a caller can bypass JavaScript and send arbitrary requests. The receiving service must therefore reject unexpected origins and paths, validate the JSON fields, limit request rates by an appropriate server-side identity, cap body size and avoid logging sensitive headers. CONTRACT-feedback.md contains the endpoint contract and a reference Worker implementation.

Diagrams

Render responsive Mermaid diagrams from Markdown.

Mermaid turns text definitions into diagrams. A mermaid fence or the paired shortcode loads the pinned runtime only on pages that need it and redraws the diagram after a colour-mode change.

flowchart LR
  M[Markdown] --> H[Hugo]
  H --> S[Search index]
  H --> P[HTML and print]
md
```mermaid
flowchart LR
  M[Markdown] --> H[Hugo]
  H --> P[HTML and print]
```

The versions and delivery options are documented under Dependencies.

Code blocks

Configure syntax highlighting, filenames, line numbers, highlighted lines and linkable anchors.

The theme renders fenced Markdown code through Hugo’s Chroma syntax highlighter and adds a header with the language or optional filename plus a copy button.

Basic fence#

Put the language after the opening fence. Add the theme-specific filename attribute inside braces when readers should know which file the example belongs to:

md
```python {filename="report.py"}
print("ready")
```

Per-block options#

The theme handles filename; Hugo handles the remaining highlighting options.

OptionDefaultValues and effect
filename="report.py"Language nameShow a filename in the theme’s code header
linenos=falseSite settingHide line numbers for this block
linenos=tableSite settingPut numbers in a separate, copy-friendly column
linenos=inlineSite settingPut a number inside each highlighted line
linenostart=201Start the displayed numbering at 20
hl_lines=[3,"6-8"]NoneEmphasize individual lines and inclusive ranges
anchorlinenos=truefalseTurn displayed line numbers into links
lineanchors="example-"EmptyPrefix anchor IDs so blocks do not collide

Options may be combined:

checks.py
20
21
22
23
24
25
def verify(build):
    # Reject output that cannot be reproduced.
    if not build.deterministic:
        raise ValueError("build output changed")

    return "ready"

The Markdown source is:

md
```python {filename="checks.py", linenos=table, linenostart=20, hl_lines=[2,"4-5"], anchorlinenos=true, lineanchors="checks-"}
def verify(build):
    # Reject output that cannot be reproduced.
    if not build.deterministic:
        raise ValueError("build output changed")

    return "ready"
```

Use a unique lineanchors prefix for every block on a page. Without it, two blocks can generate the same HTML IDs.

Site-wide defaults#

Configure defaults in the site’s hugo.toml:

hugo.toml
[markup.highlight]
  lineNos = false
  lineNumbersInTable = true
  noClasses = false
  tabWidth = 4

Fence attributes override these values for one block. noClasses = false is important for this theme because it lets the theme stylesheet control colours in light and dark mode instead of inserting fixed inline colours.

Use per-block options to explain a particular example; use site-wide defaults only for conventions that should apply to nearly every code sample. Hugo’s syntax-highlighting reference documents the complete Chroma configuration.

Authoring recommendations#

  • Specify the real language so Chroma can tokenize it correctly.
  • Use filename when the code belongs to a named file.
  • Highlight only the lines discussed by the surrounding prose.
  • Prefer linenos=table when readers will copy code.
  • Do not use a terminal fence for source code; use the terminal shortcode for command sessions and their output.

Accessibility

Keyboard behavior, reader preferences and author responsibilities.

The theme provides semantic landmarks, a skip link, visible keyboard focus, 44-pixel primary targets, accessible menus and dialogs, reduced-motion support, scalable type and non-text contrast for interactive controls.

Reader controls#

The accessibility menu stores preferences locally:

  • text sizes from 112% to 200%;
  • high contrast;
  • a deliberately prominent three-pixel Strong focus ring;
  • underlined prose links;
  • reduced motion; and
  • expanded text spacing.

These controls supplement operating-system preferences. They do not replace the browser’s own zoom or accessibility tools.

High contrast strengthens foreground roles and interactive boundaries. It does not underline links. Underlined links is an independent preference so readers can enable either treatment or combine both.

To see Strong focus ring, enable it, then press Tab rather than clicking. Keyboard focus around the search field, menu buttons, links and form controls changes to a three-pixel orange outline with a three-pixel gap. Browsers normally hide :focus-visible for pointer clicks, so clicking a control is not a reliable demonstration. The preference persists in local storage for this site origin.

Author responsibilities#

Supply meaningful image alternatives, use headings in order, label icon-only shortcodes, avoid autoplay, and do not communicate status through colour alone. Run the keyboard and axe checks documented in TESTING.md after changing layouts.

Set params.accessibilityMenu = false only if the host product supplies equivalent controls elsewhere.