Admin page
Member management, roles, status badges and a responsive data table.
Install, configure, author, extend and maintain the projectious.work Hugo theme.
Printed August 26, 2026 · 41 pages
Member management, roles, status badges and a responsive data table.
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.
Install Hugo 0.128.0 or newer, Go, Node.js/npm and Git. Confirm them before creating files:
hugo version
go version
node --version
npm --version
git --versionThe example is verified with Hugo 0.164.0. Use Hugo Extended because the CSS pipeline invokes Tailwind through Hugo Pipes.
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.0Allow Hugo to invoke Tailwind while retaining its standard executable policy:
[security.exec]
allow = ["^(dart-)?sass$", "^go$", "^git$", "^node$", "^postcss$", "^tailwindcss$"]Hugo 0.165 and later reject css.TailwindCSS when tailwindcss is absent
from this allowlist.
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.
Add the module import to the new site’s hugo.toml:
[module]
[[module.imports]]
path = "github.com/projectious-work/brand-theme-hugo-vanilla"Fetch and pin a released version:
hugo mod get github.com/projectious-work/brand-theme-hugo-vanilla@v0.3.3
hugo mod tidyCommit go.mod and go.sum. Production sites should use a release tag, not a
moving branch.
Clone or unpack the theme beside the site:
workspace/
├── brand-theme-hugo-vanilla/
└── example-docs/Keep the same module import, then add a local replacement to the site’s go.mod:
replace github.com/projectious-work/brand-theme-hugo-vanilla => ../brand-theme-hugo-vanillaHugo 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.
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.
content/
├── _index.md
└── docs/
├── _index.md
└── first-page.md+++
title = "Example documentation"
tagline = "Documentation for the example product."
++++++
title = "Documentation"
description = "Product and operations documentation."
weight = 10
++++++
title = "First page"
description = "The first page in the documentation set."
weight = 10
icon = "book"
+++
Write the page in ordinary Markdown.hugo server --disableFastRenderOpen the URL printed by Hugo. Check the sidebar, search, language and appearance
menus, code colours and keyboard focus. A production build is hugo --minify.
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.
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.
| File | Purpose | Maintained by |
|---|---|---|
hugo.toml | Site URL, module, languages, menus, outputs, Markdown and theme parameters | Site owner |
go.mod | Hugo Module dependency and selected theme version | Site owner |
package.json / lockfile | Tailwind CLI and browser-test tools | Site owner |
data/cdn.yaml | Exact KaTeX, Mermaid and asciinema runtime versions | Theme maintainer |
i18n/*.toml | Translated interface labels | Theme maintainer or translator |
| Page front matter | Title, order, description and per-page capabilities | Content 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.
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 = truebaseURL must include any deployment prefix. The theme resolves internal links
through Hugo so /project-name/ deployments work correctly.
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.
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
[security.exec] # Permit only the build tools the site intentionally invokes.
allow = ["^(dart-)?sass$", "^go$", "^git$", "^node$", "^postcss$", "^tailwindcss$"]
[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 = 0 # Initially expanded sidebar levels.
[params.brand] # Optional product identity; every key has this default.
home = "/"
wordmark = "projectious.work"
markLight = "logo/icon-light.svg"
markDark = "logo/icon-dark.svg"
favicon32 = "logo/favicon-32.png"
appleTouchIcon = "logo/apple-touch-icon-180.png"
[[params.versions]] # One published documentation version.
label = "v1.0" # Menu label.
url = "/" # Deployment-relative root for this version.
note = "latest" # Optional status annotation.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.
| Format | What the theme produces | Why enable it |
|---|---|---|
SearchIndex | /index.json with pages and H2/H3 headings | Header search and command palette |
Print | print.html for an entire section | Printable handbook/PDF workflow |
Markdown | index.md beside each page | Copy/View Markdown actions and agent consumption |
LLMS | /llms.txt with language-aware page links | A compact discovery file for LLM tools |
RSS | XML feeds styled by feed.xsl | Release-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.
Copy the example [markup] block to enable:
Turn markup.goldmark.renderer.unsafe on only when your own trusted content needs
raw HTML. The theme itself does not require it.
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.
| Parameter | Type and default | Configure when | Details |
|---|---|---|---|
description | string, empty | Setting site metadata | Header and navigation |
github | URL, unset | Showing a repository header action | Header and navigation |
brand | table, projectious.work defaults | Replacing wordmark, marks and favicons without template overrides | Header and navigation |
editURL | URL or language map, unset | Showing Edit-this-page links | Editing and feedback |
version | string, unset | Labelling the current documentation | Versioning |
versionMenuLabel | string, current version | Giving the version menu an independent label | Versioning |
versions | array, empty | Linking published documentation trees | Versioning |
versionProbe | boolean, true | Disabling same-page version probing globally | Versioning |
sidebarSections | string array, ["docs"] | Putting the sidebar on other sections | Header and navigation |
sidebarOpenDepth | integer, 0 | Changing initially expanded levels | Header and navigation |
codeTheme | adaptive or dark, adaptive | Forcing code and terminal panels dark | Tailwind and design tokens |
darkSurface | navy, unset | Making navy the initial dark surface | Accessibility |
announcement | table, unset | Showing a dismissible notice | Header and navigation |
feedbackEndpoint | URL, unset | Sending page votes to a service | Editing and feedback |
selfHostAssets | boolean, false | Replacing public CDN URLs | Dependencies |
math | boolean, false | Loading KaTeX on every page | Mathematics |
search | boolean, true | Disabling search and its index | Search |
feedback | boolean, true | Hiding the page-vote control | Editing and feedback |
accessibilityMenu | boolean, true | Hiding reader preference controls | Accessibility |
sidebarFilter | boolean, true | Hiding the sidebar filter | Search |
commandPalette | boolean, true | Disabling the keyboard palette | Header and navigation |
build.tailwind | boolean, true | Building without Node/Tailwind | Tailwind and design tokens |
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.
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 is an optional pre-build operation:
python3 -m venv .venv
. .venv/bin/activate
pip install -r scripts/requirements.txt
./scripts/notebooks.shIt 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.
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:
User-agent: *
Disallow: /search/
Disallow: /*/print.html
Sitemap: https://docs.example.com/sitemap.xmlllms.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:
# 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.
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.
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.
Check out the source tag for the older documentation, for example v0.2.0.
Build that checkout with a base URL ending in /v0.2/:
hugo --baseURL https://docs.example.com/v0.2/ --destination public/v0.2Publish 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.
Verify https://docs.example.com/v0.2/ and representative child pages.
Return to the current source and add the selector entry shown below.
Deploy current documentation without deleting the already-published v0.2/
directory.
[params]
version = "v0.3"
versionMenuLabel = "Releases" # Optional independent control label.
[[params.versions]]
label = "v0.3"
url = "/"
note = "latest"
[[params.versions]]
label = "v0.2"
url = "v0.2/"versionMenuLabel changes only the trigger and menu heading. The footer and
selected entry continue to use version.
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.
This repository lists archived tags in scripts/docs-archives.txt and the complete
cross-version navigation catalog in scripts/docs-versions.toml. The deployment
script rebuilds every listed immutable tag below its matching prefix and injects
the current catalog before it publishes the current root. A clean checkout thus
reproduces the complete version menu in every archived site without relying on
state left by an earlier deployment. Consumers can use the same pattern or retain
their archived generated trees by another deployment method.
Operational metrics, activity trends and current pipeline state.
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.
+++
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 article cover image.
coverAlt = "API response" # Required alternative text for the cover.
+++| Key | Type and default | Effect |
|---|---|---|
title | string, required | Page title and default navigation label |
linkTitle | string, title | Shorter navigation label |
description | string, empty | Lede, SEO description and search excerpt |
weight | integer, 0 | Sidebar, card and previous/next order |
icon | Tabler name, page icon | Generated overview-card icon |
toc | boolean, true | Show or hide heading navigation |
cards | boolean, true | Generate cards for children of a section |
hidden | boolean, false | Exclude a child from generated cards |
math | boolean, false | Load KaTeX on this page |
private | boolean, false | Exclude from search and sitemap when true |
cover | path, unset | Article cover image |
coverAlt | string, empty | Alternative 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.
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.
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:
+++
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 block | What it does | Who invokes it |
|---|---|---|
| Shortcode | Embeds a reusable component inside Markdown | Content author |
| Partial | Reuses HTML inside another template | Template author |
| Layout | Defines the structure of a complete page or section | Hugo |
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.
The theme is a dependency of your Hugo site. Your site should have a structure similar to this:
my-site/
├── assets/
├── content/
│ └── docs/
├── layouts/
├── hugo.toml
├── go.mod
└── package.jsonAll 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:
hugoIf this command fails, first complete the from-scratch installation.
Hugo templates are HTML with instructions between {{ and }}. These are
called template actions. The first example uses only five ideas:
| Syntax | Meaning |
|---|---|
{{ .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.
Create layouts/shortcodes/status-panel.html in your site:
<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:
{{< status-panel title="Release status" >}}
The **documentation build** passed all checks.
{{< /status-panel >}}Run the development server and open the page containing the shortcode:
hugo serverAt 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.
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:
[build.buildStats]
enable = trueDo 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:
npm install --save-dev @tailwindcss/cli@^4.1.0The 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.
Replace the simple shortcode with this styled version:
{{- $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:
{{< status-panel title="Release status" kind="important" >}}
The documentation build passed all checks.
{{< /status-panel >}}Read it from top to bottom:
$kind reads an optional argument and falls back to info.$classes maps the two supported values to complete class strings.index selects one class string for the <aside> element..Get "title" writes the heading supplied by the author..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.
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.
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:
{{ 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.
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:
layouts/ directory.layouts/.Copy the relevant theme layout into the same path in the consuming site, for
example layouts/docs/single.html. Preserve the base template contract:
{{ 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.
| Symptom | Likely cause | What to check |
|---|---|---|
template for shortcode ... not found | Wrong path or filename | Use layouts/shortcodes/<name>.html in the site root |
| The shortcode text appears literally | Escaped example syntax was copied | Use real {{< ... >}} delimiters without the documentation escapes |
.Inner is unavailable | Closing tag is missing | Add {{< /status-panel >}} |
| New classes have no effect | CSS was not rebuilt or the class is dynamic | Enable build stats, use literal classes and run the full build |
| Colours work only in one mode | Raw colour values were used | Choose semantic theme utilities or variables |
| A copied layout loses theme features | The base-template contract changed | Start 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.
Run a production build and the consuming site’s tests:
hugo --minifyCheck 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:
kind="important";When upgrading the theme:
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.
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.
Grouped form controls, validation, switches and save actions.
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/.
{{< asciinema src="/casts/theme-tour.cast" rows="8" cols="80"
idleTimeLimit="1.5" >}}cols, rows, speed, idleTimeLimit, autoplay, loop, controls, fit
and theme are supported. theme="auto" follows the site’s light/dark mode;
pass an asciinema palette name to keep a recording on a fixed palette.
Data-driven layouts can use the same player without copying shortcode markup:
{{ partial "asciinema.html" (dict
"page" . "src" .Params.cast "id" "release-demo"
"controls" "auto" "fit" "width" "theme" "auto") }}Autoplay is off by default. The player version is pinned in data/cdn.yaml; see
Dependencies for CDN and self-hosting options.
Three clear service tiers with one emphasized recommendation.
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.
The theme maps brand tokens in assets/css/theme-layer.css:
| Kind | Utilities | Meaning |
|---|---|---|
| Fonts | font-display, font-sans, font-mono | Plus Jakarta Sans, Source Sans 3, IBM Plex Mono |
| Colours | bg-page, bg-surface, bg-subtle, bg-terminal | Page, raised, subtle and terminal surfaces |
| Text | text-hi, text-lo, text-accent | Primary, supporting and accent text |
| Borders | border-default, border-accent | Standard and accent boundaries |
| Radius | rounded-sm, rounded-md, rounded-lg, rounded-xl | 3, 6, 9 and 13 pixels |
| Spacing | standard Tailwind multiples of 4px | p-1 = 4px, gap-6 = 24px |
| Breakpoints | sm, md, lg, xl | 640, 768, 1024 and 1280 pixels |
<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>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.
Compose badges, card collections, data tables and application shells from Hugo data.
The theme exposes stable partials for project layouts that render structured data. Pass dictionaries to the partials instead of copying internal markup.
badge.html accepts label, optional variant = "accent", and optional
href. The content shortcode delegates to the same partial.
{{ partial "badge.html" (dict "label" "Ready" "variant" "accent") }}card-list.html accepts items. Each item may contain eyebrow, title,
description, status = { label, variant }, and href. Callers may preserve
their own group headings and reverse their data before passing each group.
{{ partial "card-list.html" (dict "items" site.Data.roadmap.phases) }}data-table.html accepts rows, columns = [{ key, label }], and an
accessible label. It uses the same responsive wrapper as Markdown tables.
{{ partial "data-table.html" (dict
"label" "Releases"
"rows" site.Data.releases
"columns" (slice
(dict "key" "version" "label" "Version")
(dict "key" "date" "label" "Released"))) }}app-shell.html promotes the reference dashboard shell to a public partial.
It accepts page, brand, navLabel, navGroups, optional footer, and
rendered content. Navigation groups contain label and items; each item
accepts label, url, optional icon, and optional active.
{{ partial "app-shell.html" (dict
"page" .
"brand" "Operations"
"navGroups" site.Data.dashboard.navigation
"footer" "<strong>Production</strong>"
"content" .Content) }}Changelog pages accept a front-matter badges array. Both list and single
layouts render it through the public badge partial.
[[badges]]
label = "stable"
variant = "accent"Observability states for empty, loading, successful and failed runs.
Use Hugo taxonomies for related-content and release-note metadata.
Tags are ordinary Hugo taxonomy terms. Add them to front matter:
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.
A focused long-form article with metadata and a reading rail.
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.
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 baselinesHugo’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.
Do not override the theme’s complete styles.html or scripts.html pipelines.
Create layouts/partials/hooks/styles-end.html or
layouts/partials/hooks/scripts-end.html in your site instead. The theme calls
these public, empty-by-default hooks after its own assets in both Tailwind and
Hugo-only builds.
For example, compile assets/scss/site.scss, then fingerprint it and emit SRI:
{{ with resources.Get "scss/site.scss" }}
{{ $built := . | css.Sass (dict "targetPath" "css/site.css") | minify | fingerprint "sha384" }}
<link rel="stylesheet" href="{{ $built.RelPermalink }}"
integrity="{{ $built.Data.Integrity }}" crossorigin="anonymous">
{{ end }}Build and protect assets/js/site.js the same way:
{{ with resources.Get "js/site.js" }}
{{ $built := . | js.Build (dict "minify" hugo.IsProduction "target" "es2018") | fingerprint "sha384" }}
<script src="{{ $built.RelPermalink }}" integrity="{{ $built.Data.Integrity }}"
crossorigin="anonymous" defer></script>
{{ end }}The complete public contract is in the root API.md.
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:
npm install --save-exact @tabler/icons@3.31.0[[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:
{{< icon name="rocket" class="ico--lg" label="Rocket from Tabler Icons" >}}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.
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.
npm install
./scripts/build.sh
./scripts/verify.sh
./scripts/serve-watch.sh startbuild.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.
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.
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.
/ to focus it.Ctrl/Cmd+K for the command palette.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.
A chronological product history with compact semantic change labels.
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.
Read the release notes and compare configuration examples.
Update the pinned Hugo Module version:
hugo mod get github.com/projectious-work/brand-theme-hugo-vanilla@vX.Y.Z
hugo mod tidyUpdate locked Node dependencies with npm install when the release changes
package.json.
Compare local template overrides with their new upstream versions.
Build with the production baseURL, run link and browser checks, and review
visual changes in every supported language.
Commit go.mod, go.sum, package lockfiles and required configuration changes
together.
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:
chmod +x scripts/check-theme-update.sh
./scripts/check-theme-update.shIt 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:
./scripts/check-theme-update.sh --updateThe 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.
npm audit and review direct dependency updates.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.
This repository uses local scripts rather than GitHub workflows. From a clean,
synchronized main after review:
./scripts/release.sh vX.Y.ZThe 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.
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.
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:
+++
math = true
+++
Inline: \\( t_{build} < 1s \\)
$$
T_{publish} = T_{build} + T_{verify} + T_{deploy}
$$For generated expressions that are awkward to place directly in Markdown, the paired shortcode is equivalent:
{{< math display="block" >}}
T_{publish} = T_{build} + T_{verify} + T_{deploy}
{{< /math >}}See Page configuration for the front-matter contract and Dependencies for the runtime pin.
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.
| Component | Version | Delivery | Purpose | Licence |
|---|---|---|---|---|
| Hugo | 0.128.0 minimum; verified with 0.164.0 | Build tool | Static-site generation and asset pipeline | Apache-2.0 |
| Go | 1.22 module declaration | Build tool | Hugo Module resolution | BSD-3-Clause |
@tailwindcss/cli | 4.3.3 locked | npm development dependency | Compile utility CSS from Hugo build statistics | MIT |
| Playwright test | 1.62.1 locked | npm development dependency | Browser behavior and visual regression tests | Apache-2.0 |
| Tabler Icons | 3.31.0 locked | npm development dependency mounted into Hugo assets | Complete outline icon catalogue | MIT |
| IBM Plex Mono font package | 5.3.0 locked | npm development source for bundled WOFF2 cuts | Code and syntax typography | SIL OFL 1.1 |
| FlexSearch | 0.8.143 | Bundled browser asset | Local full-text search | Apache-2.0 |
| KaTeX | 0.18.4 | Pinned CDN or self-hosted | Mathematics rendering | MIT |
| Mermaid | 11.16.1 | Pinned CDN or self-hosted | Diagram rendering | MIT |
| asciinema-player | 3.17.0 | Pinned CDN or self-hosted | Terminal recording playback | Apache-2.0 |
| nbconvert | 7.16.6 | Optional pinned Python tool | Convert Jupyter notebooks to Markdown | BSD-3-Clause |
| Bundled fallback icons | 38 theme glyphs | Bundled assets | Offline interface fallback | MIT |
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.
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/.
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.
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.
This preview represents Markdown produced by the pinned nbconvert workflow. A
real notebook can include narrative cells, highlighted input and tabular output.
pages = 98
languages = ["English", "Deutsch", "Français"]
pages_per_language = pages / len(languages)
pages_per_language32.67| Language | Published |
|---|---|
| English | yes |
| Deutsch | yes |
| Français | yes |
{{< notebook "theme-demo" >}}Create an isolated conversion environment and convert every notebook:
python3 -m venv .venv
. .venv/bin/activate
pip install -r scripts/requirements.txt
./scripts/notebooks.shThe 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.
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.
defaultContentLanguage = "en"
[languages.en]
label = "English"
weight = 1
[languages.de]
label = "Deutsch"
weight = 2
[languages.fr]
label = "Français"
weight = 3The 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.
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.
Use Hugo menus in hugo.toml:
[[menus.main]]
name = "Documentation"
pageRef = "/docs"
weight = 10
[[menus.main]]
name = "Change log"
pageRef = "/changelog"
weight = 20Prefer pageRef for site pages and url for external destinations. Ordering is by
weight.
Set params.activeSection on a menu entry when it owns an entire section. This
prevents a direct shortcut and its parent section from both appearing active:
[[menus.main]]
name = "Documentation"
pageRef = "/docs"
[menus.main.params]
activeSection = "docs"Below 30rem the wordmark collapses automatically while the linked brand mark and accessible name remain available.
Documentation section groups are collapsed on a reader’s first visit. Configure
the number of initially open levels below [params]:
[params]
sidebarOpenDepth = 0 # 0 closes all; 1 opens top-level groups.The small Expand all or Collapse all button beside the sidebar title
changes every group at once. Opening or closing an individual group stores the
complete tree state in browser-local storage, scoped to the site and language.
That reader state takes precedence over sidebarOpenDepth on later page loads,
so following links does not reset the tree. Filtering opens matching groups only
temporarily and restores the stored state when the query is cleared.
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.
Connect pages to their source files and optionally collect useful reader votes.
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:
[params]
editURL = "https://github.com/org/repo/edit/main/content/"Use a language map when each language has its own content root:
[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.
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:
{"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.
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]
```mermaid
flowchart LR
M[Markdown] --> H[Hugo]
H --> P[HTML and print]
```The versions and delivery options are documented under Dependencies.
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.
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:
```python {filename="report.py"}
print("ready")
```The theme handles filename; Hugo handles the remaining highlighting options.
| Option | Default | Values and effect |
|---|---|---|
filename="report.py" | Language name | Show a filename in the theme’s code header |
linenos=false | Site setting | Hide line numbers for this block |
linenos=table | Site setting | Put numbers in a separate, copy-friendly column |
linenos=inline | Site setting | Put a number inside each highlighted line |
linenostart=20 | 1 | Start the displayed numbering at 20 |
hl_lines=[3,"6-8"] | None | Emphasize individual lines and inclusive ranges |
anchorlinenos=true | false | Turn displayed line numbers into links |
lineanchors="example-" | Empty | Prefix anchor IDs so blocks do not collide |
Options may be combined:
The Markdown source is:
```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.
Configure defaults in the site’s hugo.toml:
[markup.highlight]
lineNos = false
lineNumbersInTable = true
noClasses = false
tabWidth = 4Fence 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.
filename when the code belongs to a named file.linenos=table when readers will copy code.Add linked actions and compact status labels.
{{< button label="Read the documentation" href="/docs/" >}}
{{< button label="Search" href="/search/" variant="secondary"
icon="search" >}}variant is primary by default or secondary. icon accepts any available
Tabler icon name.
v0.3 latest
{{< badge "v0.3" >}}
{{< badge label="latest" variant="accent" >}}Badges label compact metadata; use a button or ordinary link for an action.
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.
The accessibility menu stores preferences locally:
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.
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.
Emphasize supporting, successful, cautionary or critical information.
Information a reader needs but did not ask for.
The documentation build completed successfully.
Preview configuration changes before publishing them.
The referenced page was not found.
{{< callout type="warning" title="Careful" >}}
Preview configuration changes before publishing them.
{{< /callout >}}type accepts info, note, success, warning, error or important.
The optional title replaces the localized default label.
Present related choices, pages or resources as responsive cards.
Section overview cards are generated from child-page front matter. Write cards manually only for a collection that is not a page list:
Write pages with Markdown and focused shortcodes.
Choose navigation, outputs and optional features.
{{< 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. card also accepts image and alt.
The subtitle value supports Markdown.
Hide optional detail behind an accessible disclosure control.
Titles, descriptions, breadcrumbs, tags, H2 and H3 headings, and page text.
{{< details title="What does the search index contain?" >}}
Titles, descriptions, breadcrumbs, tags, headings and page text.
{{< /details >}}Add open="true" to start expanded. Do not hide information that every reader
must see before completing a task.
Explain project structure with nested folders and files.
{{< filetree >}}
{{< folder name="content" >}}
{{< file name="_index.md" note="landing" >}}
{{< folder name="docs" >}}
{{< file name="getting-started.md" >}}
{{< /folder >}}
{{< /folder >}}
{{< /filetree >}}folder accepts closed="true". file accepts note and an optional icon.
Use bundled fallback icons and the mounted Tabler icon library.
{{< icon "brand-github" >}}
{{< icon name="printer" class="ico--lg" label="Print" >}}Use the filename without .svg. label gives a meaningful icon a text
alternative; decorative icons omit it and become aria-hidden. Browse all names
in the Tabler icon library. Theme-owned icons in
assets/icons/ take precedence and provide the small fallback set.
Publish responsive figures, captions, dark variants and lightboxes.
{{< image src="/img/sunrise-brand.svg"
alt="Orange sunrise over layered navy mountains"
caption="A brand-colour sunrise illustration" >}}Prefer a page bundle and reference a sibling image by filename. Hugo then knows
its dimensions. src-dark supplies an explicit dark-mode variant. Ordinary
Markdown images use the same figure and lightbox behavior; their title becomes
the caption.
Create base-path-safe links within and between Hugo pages.
Ordinary Markdown links resolve against the deployment base path. Prefer Hugo-aware content references when a moved target should fail the build:
[Configuration](../configuration/_index.md)
[Output formats](../configuration/site-wide.md#output-formats)
[Link within a page](#links)External links open in a new tab with safe relationship attributes and an icon. See the content authoring guide for fragment and cross-language examples.
Present ordered procedures with Markdown or structured components.
Use the minimum version listed in the theme README.
Add the theme import and required output formats.
Run the local build and review every colour mode.
{{< steps >}}
{{% step title="Install Hugo" %}}
Use the supported version.
{{% /step %}}
{{% step title="Verify" %}}
Run the local build.
{{% /step %}}
{{< /steps >}}Step bodies may contain cards and other components. Use < delimiters around a
step that contains nested shortcodes; use % for Markdown prose. CSS counters
renumber the steps when their order changes.
Run the build and review every colour mode.
{{< steps >}}
{{< step title="Choose a starting point" >}}
{{< cards cols="2" >}}
{{< card title="New site" link="/docs/getting-started/" >}}
{{< card title="Existing site" link="/docs/configuration/" >}}
{{< /cards >}}
{{< /step >}}
{{< /steps >}}Place equivalent alternatives in a keyboard-accessible tab set.
npm installpnpm installgo mod download{{< tabs items="npm, pnpm, Go" >}}
{{< tab >}}First panel.{{< /tab >}}
{{< tab >}}Second panel.{{< /tab >}}
{{< tab >}}Third panel.{{< /tab >}}
{{< /tabs >}}The number and order of comma-separated labels must match the tab children.
Arrow keys move focus between tabs.
Show static command sessions with adaptive terminal styling.
$ hugo server --disableFastRender
Watching for changes in content and layouts
Built in 284 ms
Web Server is available at http://localhost:1313/
{{< terminal title="deploy" >}}
$ hugo server --disableFastRender
Watching for changes in content and layouts
Built in 284 ms
{{< /terminal >}}Use this shortcode for fixed output. Use Terminal recordings for a timed, playable session and Code blocks for source code readers should copy or modify.
Keep recurring definitions consistent through a shared glossary.
A module installs the theme. A page bundle keeps related page resources together.
A {{< term "module" >}} installs the theme.
{{< term key="page-bundle" label="Page bundles" >}} keep resources together.Definitions live in data/glossary.yaml. The first positional value or key
selects an entry; label overrides the visible text without duplicating the
definition.