Unbloated for Hugo

Rendered Example

Browse the Example Site

Documentation destinations: stable and development.

This adapter extracts the presentation layer of Jorge’s website. Its stylesheet uses the Unbloated core’s design variables; the Hugo templates supply the existing navigation, post layouts, search, and shortcodes.

The published guide includes a Rendered Example link to a site built with the actual theme. Its example configuration and content include a home page, article, contents collection, search page, and tab shortcodes. Documentation builds use the root’s PostCSS CLI and publish the generated pages and assets under the same dev or stable prefix as the guide. Search retains the theme’s CDN dependencies described below.

Build

From the Unbloated repository root:

npm ci --ignore-scripts
npm run build:adapters
npm run check:adapters

The build places the unchanged core CSS and its provenance under assets/css. The unbloated-styles.html partial combines the core and adapter CSS, then fingerprints the result. Missing core CSS is a build error.

Use With the Website

Keep the theme and web checkouts next to each other. The website configuration selects theme: hugo and themesDir: ../theme/packages. Build the adapter before running Hugo in web.

The website retains its content, configuration, analytics and SEO partials, application CSS, and section-specific layout overrides. Shared layouts and the main stylesheet belong here. Hugo uses the website’s PostCSS configuration and its npm-installed PostCSS dependencies. Use Hugo Extended 0.144.1 or newer, and retain the website’s pinned version in deployment.

For a different checkout location, set HUGO_THEMESDIR to the directory containing the hugo theme. CI checks out this monorepo into theme-source, builds its assets, and sets HUGO_THEMESDIR=theme-source/packages.

Distribution

npm run pack:adapters from the repository root writes a Hugo npm tarball into .cache/artifacts/. The archive contains layouts, assets, static scripts, the theme manifest, and license notices. It is not published automatically.

Extract the archive’s package/ directory into a site’s themes/hugo/ to use it as a traditional Hugo theme. A packaged theme includes the core CSS already; the consuming site still needs its Hugo and PostCSS build tools.

License Files

The adapter uses LGPL-3.0-only. In this checkout, the canonical license, incorporated GPL terms, and copyright notice are the root LICENSE, COPYING, and NOTICE. Adapter staging includes their exact bytes under assets/css/licenses/unbloated/, which is shipped in the npm tarball. Separate top-level copies in this package are unnecessary. Keep the bundled license directory when redistributing the theme.

Content and Navigation Contracts

Primary menus use native links and Hugo pageRef relationships for current-page and ancestor state. Inline prose links stay underlined. Wide table cells and long identifiers wrap inside the content width. Plant cards emit semantic definition lists during generation, with no script dependency.

private: true and noindex: true exclude pages from search, RSS feeds, sitemap entries and translated alternate destinations, and theme collections. Taxonomy landing pages are also excluded from the sitemap and marked noindex. These flags do not restrict access: the page HTML is still public. Keep genuinely nonpublic sources out of the public build.

Search requires a home JSON output (outputs.home: [HTML, RSS, JSON]) and a page with layout: search. It fetches this site’s generated index URL, including its base path. URLSearchParams uses the first s value, decodes plus signs and encoded separators, and tolerates malformed escapes. Empty input prompts for a query. Failed index requests or unavailable Fuse.js produce a visible recovery message. Result labels are literal text; destinations must share the index’s origin and deployment path. Mark.js is optional for highlighting.

Plotly shortcodes accept json and optional CSS height, generate distinct IDs for repeated datasets, and report load failures in their own container. The page must supply Plotly (the existing plotly page parameter enables its script).

Hugo Extended 0.144.1 remains the minimum. The theme selects legacy locale APIs below 0.158.0 and Language.Locale/Direction in newer versions. Use languageCode in older consumer configuration and locale from 0.158.0 onward. Set the active language’s direction for RTL sites. The consumer must allow the VERSION environment variable under security.funcs.getenv if using the theme footer. PostCSS, native CSS nesting, and modern container sizing remain build/browser requirements of the adapter.

Locale and RTL regressions run with the same real-core fixture on Hugo Extended 0.144.1, 0.154.5, and 0.167.0. Select a binary with HUGO_BIN=/absolute/path/to/hugo node --test tests/hugo.test.mjs after building the core and installing the root npm dependencies.

Standalone Site Setup

Place the theme in themes/hugo/ after staging its core bundle, or use themesDir to select this repository’s packages/. Install a PostCSS CLI in the host site’s npm toolchain and any plugins required by its PostCSS configuration. The theme calls postCSS during stylesheet processing; missing tooling fails the Hugo build rather than falling back to unprocessed styles.

For Hugo 0.158.0 and newer, a site configuration can start with:

baseURL = 'https://example.org/project/'
title = 'Project Documentation'
theme = 'hugo'
locale = 'en-US'

[params]
description = 'Reference and notes for the project.'
lastposts = 10
footer = 'Project documentation'

[outputs]
home = ['HTML', 'RSS', 'JSON']

[security.funcs]
getenv = ['^HUGO_', '^CI$', '^VERSION$']

[[menus.main]]
name = 'Home'
pageRef = '/'

Use languageCode in place of locale before 0.158.0. Configure direction for a current RTL language or languageDirection on the older versions. Set a numeric params.lastposts; the home template uses it as the latest-post limit. Add a content/search.md with layout: search in its front matter for the header search form, and place posts under content/posts/.

The imported masthead always renders social destinations. Supply params.email, github, linkedin, and youtube account values, or override baseof.html for a different header. The theme does not validate whether these external accounts exist. Provide static/favicon.ico and static/favicon.png, or override the head partial to use your own icons.

Page description, summary, author, keywords, tags, categories, image/images, and canonical values feed the SEO partial. Site-level description, author, keywords, images, jobTitle, and location supply defaults or person metadata. Set subtitle for a secondary post heading. application_css names an asset to process with PostCSS and include on that page.

Shortcodes

All parameters below are named. Missing values are not generally validated; supply the listed content inputs. File-inclusion shortcodes read trusted local source during generation and must not receive untrusted filenames or HTML.

Shortcode Parameters and Behavior
admonition type, content, and optional title. Types info, warning, danger, hint, and tip select a label/icon; unknown types use Information. A custom title applies to danger or the fallback type. Content is escaped text.
note content, rendered as escaped text in a note container.
euler_problem title and statement, rendered as escaped text.
card_plant name, scientific_name, type, growth_habit, watering, humidity, light, soil, temperature, and birthday; optional image. Emits definition-list fields without scripts. Omitted fields remain empty.
download_article href, used as the destination of the article-download link. Supply a real generated or hosted file.
image_with_caption src and caption. A matching page image resource produces 400/800-pixel WebP variants and an 800-pixel fallback with dimensions; other URLs use an ordinary image. Caption text also supplies the image alternative.
include file, relative to the page source directory, and language for its fenced code block. A missing file fails the build.
rawhtml Optional file relative to the page source directory; otherwise uses the inner body. Inserts trusted HTML without escaping. A missing named file fails the build.
plotly json, a fetchable Plotly figure URL containing data and layout; optional height, default 200px. Uses contextual escaping and an independent per-page container ID. Missing json shows an error paragraph; fetch or rendering failure shows feedback in the container.
threejs version, id, and src. Adds an unpkg import map and module script; the module owns rendering into threejs-container-<id>. Use distinct IDs and a consistent Three.js module version.
tabs Inner tab shortcodes; optional accessible label, sync group name, and tabRightAlign with one-based tab numbers. Automatically includes the tabs script.
tab tabName and Markdown inner content, used only inside tabs. All panels remain visible without enhancement.
version No parameters. Emits the VERSION environment value; the host must allow access to it in Hugo’s security settings.
{{< tabs label="Examples" sync="language" >}}
  {{< tab tabName="Python" >}}Python example.{{< /tab >}}
  {{< tab tabName="JavaScript" >}}JavaScript example.{{< /tab >}}
{{< /tabs >}}

For file inclusion, place sample.py and fragment.txt beside the page source:

{{< include file="sample.py" language="python" >}}
{{< rawhtml file="fragment.txt" />}}

The text fragment may contain trusted HTML. A .txt suffix keeps Hugo from discovering it as a separate HTML content page, which current content security defaults reject. Close or self-close rawhtml even when using its file option, because the shortcode also accepts an inner body.

Tabs activate on click, Arrow Left/Right, Home, or End with roving focus. Groups with the same sync value select peers by matching tab text, not position. The imported posts layout also uses title-driven branches: Categories and Tags show collections, All posts shows all discoverable posts, and other titles filter the category name in lowercase or initial-capital form. Its conventional section is posts; adapt the layout when using a different content model.

Browser Scripts and Offline Use

assets/js/tabs.js enhances only shortcode tabs. static/js/search.js expects the search layout’s #search-results[data-index-url], the masthead’s #search-query, and Fuse.js. That layout loads Fuse.js 6.6.2 and optional Mark.js 8.11.1 from cdnjs. Text remains literal; index loading and malformed data errors produce the existing recovery message.

The plotly page flag loads plotly-latest.min.js from Plotly’s CDN. The math flag loads KaTeX 0.16.21 and its automatic renderer from jsDelivr. The threejs flag loads legacy classic Three.js 0.125.2 and OrbitControls; the threejs shortcode instead supplies an ES module import map. Avoid enabling both Three.js loading paths on the same page without a deliberate compatibility setup.

These optional paths need network access and suitable script/style CSP rules; the Plotly shortcode also needs permission to fetch its data. Self-host assets and override the relevant partials for offline use or a stricter CSP. The core CSS’s no-remote-assets contract does not extend to these adapter features.

static/js/youarehere.js is retained legacy code for matching navigation button text to URL segments. Current templates derive native-link state during the Hugo build and do not load it. script-footer.html is another retained host partial, with Bootstrap and /js/main.js; the base template does not call it.

Source and Verification

layouts/_default/ contains page, collection, feed, index, and search templates; section overrides live beside it. partials/discoverable-pages.html owns the shared flag filter, language.html handles locale compatibility, seo.html emits metadata, and unbloated-styles.html handles CSS composition. assets/css/style.css owns platform layout and aliases to core properties.

From the monorepo root, run npm run build:adapters, node --test tests/hugo.test.mjs tests/search.test.mjs, and the Hugo cases in npm run test:browser. The persistent consumer fixtures use the real bundled core and local script substitutes; they do not certify live CDN availability. See Rendering Regressions for consumer browser checks and build requirements.