This is the multi-page printable view of this section. .
Appearance
OINK’s appearance customization rests on semantic CSS custom properties: a site overrides a small number of variables to reskin, without copying the theme’s component selectors.
In this chapter
- Typography presets: seven semantic font roles and two presets
- Styling: colors, layout, and Sass variables
- Advanced customization: template overrides and site-owned components
Precedence
Lowest to highest:
- theme defaults
- presets (
typography.preset) - Sass variables in
_variables_project.scss - custom-property overrides in
_styles_project.scss
Site settings always take precedence over preset defaults.
1 - Typography presets
OINK puts font choices behind seven semantic CSS custom properties. A site overrides those roles to change typography, without needing to know which selectors the theme uses internally.
Hugo compiles the whole mechanism into the same static stylesheet at build time: no JavaScript, no remote font service, and no runtime preset loader.
Two built-in presets
| Preset | Effect |
|---|---|
technical |
The default. Preserves the Chakra Petch and IBM Plex Mono treatment, with all font files bundled locally |
system |
Resets the display, metadata, print, and monospace roles to platform stacks and requests no OINK brand fonts |
The selected value is emitted as data-td-typography on <html>. An
unsupported value fails the build rather than silently changing the site.
system, OINK’s brand font files remain static theme assets;the browser simply does not request them with the stock configuration.
The seven semantic roles
-
--td-ui-font-family,CSS property Navigation, controls, and general chrome. Seeded from
--bs-body-font-family.-
--td-body-font-family,CSS property Article and blog prose. Inherits the UI role by default.
-
--td-heading-font-family,CSS property Content headings. Takes
$headings-font-family, falling back to the body role.-
--td-code-font-family,CSS property Code and terminal content. Takes
$font-family-code.-
--td-display-font-family,CSS property Wordmarks and display titles. Chakra Petch, then the UI role.
-
--td-meta-font-family,CSS property Technical labels and metadata. IBM Plex Mono, then the code role.
-
--td-print-font-family,CSS property Print-only body copy.
Theme components consume these roles or a component alias such as
--td-asciinema-font-family; they never name a font directly. The
dependency direction is always:
The reverse is prohibited — Bootstrap custom properties never reference OINK ones — which keeps the graph one-way and prevents custom-property cycles.
Using site-owned fonts
Put local .woff2 files under the site’s static/webfonts/, declare the faces
in _styles_project.scss, and override the roles you need. Hugo loads that file
after the theme styles:
must declare a CJK fallback, or Chinese characters drop to the browser default and stop aligning with Latin characters inside code blocks:
Remote URLs and arbitrary CSS are deliberately not accepted through YAML. Font files and stylesheets stay local, reviewable build inputs.
Per-content typography
Roles inherit normally, so giving one kind of content its own treatment needs neither a new global preset nor copied component selectors:
Compatibility with existing configuration
Existing Sass customization remains the first input to this system. OINK reuses established variables rather than introducing parallel knobs:
| Existing variable | OINK interpretation |
|---|---|
$font-family-base / $font-family-sans-serif |
Bootstrap body font, then the UI and body roles |
$headings-font-family |
The heading role when explicitly configured |
$font-family-monospace |
The Bootstrap monospace base |
$font-family-code |
The code role, and ordinary code, pre, kbd, samp |
$td-google-font-name |
The default print face |
Declare these in assets/scss/_variables_project.scss, as in Docsy; they
compile into the role defaults.
Custom-property overrides in _styles_project.scss run later and therefore stay
available for contextual theming. Project settings intentionally take
precedence over preset defaults.
Scope
This is the first token slice, and it covers typography only. Semantic colour, surface, radius, density, and appearance presets are not included; they should arrive separately, once their Bootstrap and shell-token contracts have matching regression coverage.
Next steps
- Styling: colors and layout
- Advanced customization: template overrides
2 - Look and feel
OINK ships a complete visual system built on Bootstrap and Docsy, with local fonts, icons, styles, and browser code. A consuming site can change tokens and project styles without rebuilding a Node dependency tree.
Project styles
Hugo Extended compiles the theme’s SCSS through Hugo Pipes. Project overrides participate in the same bundle, so production builds can minify, fingerprint, and integrity-check one same-origin stylesheet.
Project style files
Override these files in the site’s assets/scss/ directory:
| File | Purpose |
|---|---|
_variables_project.scss |
Variables set before Bootstrap and OINK defaults |
_variables_project_after_bs.scss |
Variables or maps that require Bootstrap definitions |
_styles_project.scss |
Project selectors loaded after the theme’s component styles |
Start with the smallest override:
Do not edit vendored Bootstrap, Font Awesome, or local font files for ordinary branding. A theme update would overwrite those changes and obscure the dependency boundary.
Advanced style customization
For the stable customization layers, typography presets, semantic font roles, and content-scoped patterns, read Advanced customization.
OINK’s SCSS import order is:
- Bootstrap functions;
- project variables;
- OINK defaults and Bootstrap;
- post-Bootstrap project variables;
- OINK components and local brand layer;
- project styles.
Use variables or CSS custom properties for stable design decisions. Override a selector only when no token exists, and scope it to the smallest component. Inspect both light and dark output because many colors are theme-dependent.
⚠️ Resetting internal styles
OINK’s internal partials are not a public Sass API. Importing or suppressing individual internal files couples a site to repository layout and import order. If a product needs a fundamentally different shell, override a Hugo layout or maintain a deliberate theme fork instead of resetting the entire stylesheet.
Extra styles
For isolated third-party CSS, publish a local asset from a hook:
Put the template in layouts/partials/hooks/head-end.html. Prefer the project
SCSS files when the rules belong to the site’s design system. Never use a remote
stylesheet as an implicit fallback.
Colors and color themes
Bootstrap semantic colors and OINK brand tokens are available throughout the theme. Semantic names communicate intent better than literal colors.
Site colors
Set Bootstrap variables before compilation:
OINK’s canonical layer also exposes CSS properties such as --td-brand-elev,
--td-brand-silk, --td-brand-copper, --td-brand-header-bg, and
--td-brand-mark-gradient. Override them on :root and
[data-bs-theme='dark'] as a pair:
Light/dark color theme and mode support
Color theme is the palette used by a component; color mode is the
site-wide light or dark state. OINK uses Bootstrap’s
data-bs-theme="light|dark" attribute and stores an explicit reader choice in
local browser storage. With no choice, it follows prefers-color-scheme.
Every custom component must define legible states for both modes, including hover, focus, disabled, selected, and code colors. Do not encode meaning by color alone.
Light/dark color modes
The default sample site enables color-mode support and shows the selector:
The selector updates the document before normal interaction to limit a flash of the wrong theme. OINK’s script is local and does not contact an external service.
Choosing themes or color modes for your site
Use the default automatic behavior for most sites. Choose a forced mode only when the complete visual identity has been tested in that mode and readers do not need an alternative. Screenshots are not sufficient: check real text, tables, alerts, forms, diagrams, code, and focus indicators.
How to disable dark mode
To disable dark mode and hide the menu:
The experimental value enable-only (experimental) enables theme-aware styles
without showing a selector. Treat it as transitional because the configuration
surface can change.
How to pick colors with good color-contrast
Meet WCAG contrast requirements in every component state. Test actual computed colors, including translucent layers over images. As a working minimum, normal text needs 4.5:1 contrast and large text needs 3:1; focus and non-text UI indicators also need adequate contrast. Automated tools catch common failures, but keyboard and visual review remain necessary.
Fonts
OINK does not fetch Google Fonts. Open Sans, Chakra Petch, IBM Plex Mono, and
Font Awesome files used by the theme are stored locally. The legacy Sass
variable $td-enable-google-fonts controls the bundled Open Sans faces despite
its historical name.
Set typography in _variables_project.scss:
OINK also exposes build-time typography presets and runtime-independent semantic font roles. See Advanced customization for the complete public interface and examples that scope a font to blog, OpenAPI, or code-heavy pages.
If you add a font, subset and self-host it, include the required scripts, use
font-display: swap, document its license in VENDOR.json, and test CJK
fallback. Do not make page rendering depend on a font CDN.
CSS utilities
Bootstrap utility classes are available in Markdown with raw HTML and in
layouts. Prefer semantic Markdown and OINK shortcodes for content; use utilities
for small, presentational adjustments that remain understandable at different
breakpoints. Project-wide patterns belong in _styles_project.scss.
Code blocks
OINK supports Hugo Chroma by default and a locally vendored Prism option. Choose one highlighter consistently; enabling both produces duplicate markup or styles. For filenames, Copy policies, wrapping, collapse, line anchors, and shareable Code Groups, see Code blocks and Code Groups.
Code highlighting with Chroma
Chroma runs during the Hugo build and requires no browser highlighter. Use a language identifier:
Basic Chroma style configuration
Configure markup in Hugo:
OINK expects class-based output so light and dark styles can differ. When regenerating a palette, keep the generated CSS local and review it against the brand background.
Light/dark code styles and more
The theme includes separate Chroma palettes under assets/scss/td/chroma/ and
applies them by mode. Project overrides should target .chroma beneath the
relevant theme attribute, not hard-code a global background.
Selecting console block content
Use console for terminal transcripts. OINK styles prompts and output for
selection so readers can copy commands without decorative prompt text. Keep
commands and their output on distinct lines, and never rely on color alone to
distinguish them.
Code blocks without a specified language
An unlabelled fence renders as plain code. Use it only when no grammar applies,
and label command sessions as console or bash instead of asking Chroma to
guess.
Copy to clipboard
Copy buttons are enabled for Chroma unless params.disable_click2copy_chroma is
true. Clipboard access requires a secure context in deployed browsers. The
control must remain keyboard accessible and must not copy line numbers or
prompts.
Code highlighting with Prism
Set:
to use OINK’s local prism.js and prism.css. This is a compatibility option
for existing sites; Chroma is preferred for a browser-light build.
Code blocks with no language
Prism also treats unlabelled blocks as plain text. Add the correct language class rather than enabling heuristic detection.
Extending Prism for additional languages or plugins
Build and vendor the exact Prism bundle, replace the local files in a controlled theme change, record its version and license, and add a fixture that exercises the language or plugin. Do not pull Prism components from a CDN at runtime.
Navbar
OINK’s navbar contains the project identity, main menu, version and language
selectors when applicable, color-mode control, search, and the repository link.
It renders on every layout unless a page turns it off with
navbar_enabled.
Default look and feel
The navbar uses the local brand palette and a fixed minimum height, and it has two states rather than a desktop layout plus a separate mobile menu.
On mobile
Below lg the navbar keeps the logo and turns every remaining item into a
right-aligned icon; nothing is hidden behind a hamburger. Below md, shell
pages gain one more icon that opens the sidebar drawer. Test long Chinese
labels, 200% zoom, touch targets, focus order, and both page directions — and
give every top-level menu entry a pre icon, because the icon is all that
survives at this width.
On desktop
At lg and up the main menu expands inline with its labels; version, language,
mode, search, and repository controls stay grouped at the end. Menu parents open
their panel on hover or keyboard focus and navigate on click. Avoid enough
custom entries to push controls outside the viewport.
Translucent over cover images
The blocks/cover shortcode marks the navbar as cover-aware. It starts
translucent and gains the normal background as the page scrolls.
Customizing the navbar
Use configuration for behavior and project SCSS for presentation. Preserve the landmark, focus order, accessible labels, and responsive overflow behavior when overriding the navbar partial.
Navbar height
Override $td-navbar-min-height before theme styles compile. Re-test anchor
offsets, sidebar height, mobile wrapping, and cover blocks because all depend on
this value.
Background color/opacity
Set --td-navbar-bg-color or --td-brand-header-bg in both modes. If the
background is translucent, validate contrast over every cover image and provide
a solid scrolled state.
Setting the navbar light/dark color theme
A page can set ui.navbar_theme: dark in front matter or cascade when its cover
requires light foreground controls. This changes navbar component styling; it
does not force the whole site’s color mode.
Translucent over cover images
Disable translucency site-wide with:
Prefer this when cover imagery is unpredictable or accessibility review cannot guarantee contrast.
Styling your project logo and name
Place logo partial overrides under layouts/partials/ and source assets under
assets/ or static/. Provide meaningful alternative text for informative
marks and an empty alternative for a purely decorative mark. SVGs must use a
view box and inherit or define colors for both modes.
The OINK sample uses a text wordmark with a local gradient. Change the site title in language configuration and the visual tokens in project SCSS; do not replace brand text with an image when selectable text works.
Light/dark-mode menu
The selector appears when params.ui.showLightDarkModeMenu is true. Clicking
the navbar icon toggles light and dark; hovering or focusing it opens the
System / Light / Dark picker, where System follows the reader’s operating
system. Keep it in the shared navigation so its state applies consistently
across languages and page types.
Alerts
Markdown alert types map to semantic OINK/Bootstrap styles. Customize .alert-*
and the alert render hook only as a pair, retain a visible label or icon, and
test links and inline code inside every background. See
Adding Content for syntax.
Tables
Markdown tables receive responsive and theme-aware styles. Keep cells concise, use real header cells, add a caption in custom HTML when context requires one, and test horizontal overflow on mobile. A table should not be used to position unrelated content.
Customizing templates
Hugo resolves site layouts before theme layouts. Copy only the smallest partial
that needs changing and compare it during upstream syncs; a full baseof.html
override can silently miss future accessibility and asset-pipeline fixes.
Add code to head or before body end
Use layouts/partials/hooks/head-end.html for head additions and
layouts/partials/hooks/body-end.html for scripts or closing integrations.
Self-host assets, load them only on pages that need them, and keep production
CSP compatible.
Adding a banner before page content
Override the relevant hook or content partial with a condition based on page parameters. A banner must not hide the page heading, trap keyboard focus, or shift anchor targets beneath the fixed navigation.
Adding custom class to the body element
Set body_class in page front matter or a section cascade:
OINK appends the value to its generated body classes. Use a project-specific, semantic class name and never insert untrusted content into this field.
3 - Advanced customization
OINK provides a small, layered customization contract instead of requiring a site to copy component selectors or fork the theme. Everything still passes through Hugo Extended and Hugo Pipes: no Node.js, npm, PostCSS, remote font service, or client-side preset loader is required.
This page documents the supported extension points and the first public semantic-token slice: typography. For general color-mode, code-highlighting, navbar, and template examples, see Look and feel.
Choose the right layer
Use the highest-level interface that can express the change:
| Layer | Extension point | Best for |
|---|---|---|
| Hugo configuration | hugo.yaml or page front matter |
Supported choices such as typography preset and page width |
| Sass foundation | assets/scss/_variables_project.scss |
Existing Docsy and Bootstrap variables that affect the whole bundle |
| Sass after Bootstrap | assets/scss/_variables_project_after_bs.scss |
Rare overrides that depend on Bootstrap variables or maps |
| Semantic CSS | assets/scss/_styles_project.scss |
Role tokens, a content type, one page family, or one component |
| Hugo templates | layouts/ and partial hooks |
Structural or DOM changes that CSS cannot represent |
Start with configuration or an established Sass variable. Use a semantic CSS property when a decision needs a narrower scope. Override a selector only when there is no suitable token, and override a template only when the structure itself must change.
Do not edit files inside the theme, Bootstrap, Font Awesome, or bundled font directories for site branding. Those edits are difficult to audit and will be lost or conflict during an upgrade.
How the CSS contract is layered
OINK keeps the dependency direction one-way:
Existing Docsy and Bootstrap variables remain the foundation. OINK adds a semantic role only where components need a shared meaning, such as “article body” or “technical metadata”. Bootstrap properties never point back to OINK roles, which avoids custom-property cycles.
The following API status is intentional:
- Established Docsy and Bootstrap variables remain compatible inputs where practical.
- The typography roles documented below are public site-customization APIs.
- A documented component alias, such as
--td-asciinema-font-family, has a narrower component-specific contract. - Undocumented
--td-shell-*and selector-local properties are implementation details. Do not assume every property with a--td-*prefix is public.
Typography presets
Choose a built-in site-wide preset in hugo.yaml:
| Preset | Result | Font requests |
|---|---|---|
technical |
Default OINK treatment, including Chakra Petch display text and IBM Plex Mono technical text | Uses locally bundled files only |
system |
Platform sans and monospace stacks, suitable for a neutral base or the smallest text-font footprint | Does not request OINK’s bundled text faces unless project CSS explicitly references one |
OINK writes the resolved value to data-td-typography on the <html> element.
An unsupported value stops the Hugo build instead of silently falling back. This
is a build-time site choice, not a JavaScript-powered reader preference.
The preset selects defaults; project Sass and CSS remain authoritative. For
example, a site that explicitly references IBM Plex Mono can still request it
while using the system preset.
Public font roles
Components consume roles rather than naming a brand font directly:
| CSS property | Controls | Default source |
|---|---|---|
--td-ui-font-family |
Navigation, controls, search, and general chrome | Bootstrap body font |
--td-body-font-family |
Documentation and blog prose | UI role |
--td-heading-font-family |
Article headings | $headings-font-family, or body role |
--td-code-font-family |
code, pre, kbd, samp, and terminal content |
$font-family-code |
--td-display-font-family |
Wordmarks and display titles | Chakra Petch, then UI role |
--td-meta-font-family |
Technical labels and metadata | IBM Plex Mono, then code role |
--td-print-font-family |
Printed body copy and default print headings | $td-google-font-name, then Bootstrap body font |
Change the broad semantic role in most cases. For example, Asciinema consumes
--td-asciinema-font-family, which defaults to --td-code-font-family.
Override the component alias only when terminal playback should deliberately
differ from all other code.
Reuse Docsy and Bootstrap Sass variables
OINK interprets established names instead of adding parallel Sass knobs:
| Existing variable | OINK interpretation |
|---|---|
$td-fonts-serif, $font-family-sans-serif, $font-family-base |
Bootstrap body font, then UI and body roles |
$headings-font-family |
Heading role when explicitly set |
$td-font-family-monospace, $font-family-monospace |
Bootstrap monospace foundation |
$font-family-code |
Code role and ordinary code elements |
$td-google-font-name |
Default print face |
Put these compile-time overrides in _variables_project.scss, just as in Docsy:
This changes the compiled defaults. Use semantic CSS properties in
_styles_project.scss when different areas of one site need different
treatments.
Add a site-owned font
Store reviewed .woff2 files under the consuming site’s static/webfonts/
directory, then declare and assign the face in _styles_project.scss:
For code, include an explicit CJK fallback when content can contain Chinese:
Subset and self-host fonts, include every script the site needs, use
font-display: swap, and record the license. OINK intentionally does not accept
arbitrary font URLs or CSS strings through YAML.
Scope styles by content type
Semantic properties inherit, so a content-specific treatment does not require a
second stylesheet or copied component rules. Blog pages already carry the
td-blog body class:
OINK also adds td-swagger to Swagger/OpenAPI pages. For a project-defined page
family, set body_class in front matter or a section cascade:
Use a semantic, site-owned class. Never put untrusted content in body_class.
Keep layout and typography independent
Typography, article width, and component structure are separate axes. OINK’s
existing page_width parameter supports normal, wide, and full, globally
or in page front matter:
This provides a clean foundation for distinct experiences without one all-purpose preset:
| Experience | Recommended composition |
|---|---|
| Standard documentation | page_width: normal plus the site typography preset |
| Full-width canvas | page_width: full plus a page-specific body_class if needed |
| Blog or editorial reading | td-blog scoped body and heading roles, usually with normal width |
| Code-heavy reference | A project body class that adjusts code and metadata roles |
| OpenAPI reference | The Swagger layout and td-swagger; keep renderer-specific structure in its layout |
Keeping these concerns independent prevents preset multiplication and lets a future editorial, API, or code treatment reuse the same roles.
Colors and component surfaces
Use Bootstrap variables such as $primary, $secondary, and $danger for the
compiled palette. At runtime, prefer Bootstrap semantic properties such as
--bs-body-bg, --bs-body-color, --bs-link-color, and --bs-border-color.
OINK also documents a small brand layer, including --td-brand-elev,
--td-brand-silk, --td-brand-copper, --td-brand-header-bg, and
--td-brand-mark-gradient. Override light and dark values as a pair. The
Look and feel page shows
the complete pattern.
Do not globally override a shell or component token merely because its current name looks convenient. First change its Bootstrap or documented semantic source; use a component alias only when that component must diverge.
Review checklist
Before shipping a customization:
- Build with the oldest and newest supported Hugo Extended versions.
- Check light, dark, print, forced-colors, and reduced-motion behavior.
- Review documentation, blog, code, search, and any OpenAPI pages in scope.
- Test narrow and wide viewports, including long CJK text and code lines.
- Confirm font requests are local, intentional, licensed, and no larger than necessary.
- Prefer one semantic override over repeated selector patches.
These rules preserve OINK’s central constraint: a customized site remains a single-Hugo-binary project with no additional build or runtime dependency.