Theming

Trail ships one theme, compiled into the binary. There is no template directory to override and no theme to install. What you do control is colour — through configuration for the ordinary cases, and through CSS custom properties for everything else.

The three colour settings #

accent = "#3b82f6"          # the site's colour
accent_dark = "#7aa8ff"     # optional; derived from accent if absent
product_theming = true      # default

accent is the site's colour: links, focus rings, the wordmark's accented word, the hero glow, the front-page cards. Without it the theme's own default is used.

accent_dark is the dark-mode variant. Without it, trail derives one by mixing the accent 75% toward white — a light accent reads badly on a dark background, and most brand colours need lifting. Set it explicitly when the derived one is wrong. Setting it without accent is a build error.

Both must be #rrggbb.

Each product also has a color, and by default it displaces the site accent on every page belonging to that product: cards, the monogram tile, sidebar highlights, heading numbers, RFC-2119 keywords, focus rings. product_theming = false turns this off site-wide — every per-product tint collapses to the site accent, and products keep only their monogram and their card.

Three-state light and dark #

Every reader is in one of three states, and the header toggle cycles between them:

State
SystemFollow the operating system's preference. The default.
LightPinned light, whatever the system says.
DarkPinned dark, whatever the system says.

The choice is stored per reader in their browser and applied before first paint, so a reader who has chosen dark never gets a flash of white.

In CSS this is three blocks: :root carries the light tokens; :root[data-theme="dark"] overrides them when the toggle has pinned dark; and @media (prefers-color-scheme: dark) :root:not([data-theme="light"]) overrides them for system-dark readers who have not pinned light.

The tokens #

Everything resolves through CSS custom properties, so overriding a token restyles every place it is used.

Token
--bgPage background.
--surfaceCards, menus, the sidebar — translucent over the background.
--textBody text.
--text-mutedSecondary text: crumbs, meta lines, captions.
--border, --border-strongHairlines and emphasised edges.
--accentThe site accent, as resolved for the current theme.
--pcThe product colour in scope. Falls back to --accent.
--glow, --glow-mixThe accent glow behind heroes.
--headlineThe gradient headings are painted with.
--tile-mix, --tile-fg-mixHow strongly monogram tiles take their product colour.
--card-shadowCard elevation.
--menu-bgThe products dropdown, which must be opaque.
--code-comment, --code-keyword, --code-string, --code-constant, --code-function, --code-typeThe syntax-highlighting palette.

--pc is the one to know. It is set inline on every element scoped to a product, which is how one stylesheet tints five products differently — and why product_theming = false can collapse them all with a single rule.

Syntax highlighting follows the theme #

Code is highlighted at build time into class names, not inline colours: hl-comment, hl-keyword, hl-string and so on. Those classes resolve through the six --code-* tokens above, which are redefined in each theme state — which is why code goes light and dark with the rest of the page. Override those six tokens to change the code palette; see Code blocks.

Fonts #

Two variable fonts ship with the site and are served from /assets/fonts/: Inter for body text and Space Grotesk for headings. Both are self-hosted — no third-party font request — and their licences ship beside them, as the SIL Open Font License requires.

To use different fonts, add @font-face rules and a font-family override in your custom stylesheet. The built-in fonts are still emitted; there is no setting to suppress them.

The custom stylesheet #

custom_css = "custom.css"

The named file ships at /assets/custom.css and is loaded after the built-in stylesheet, on every page.

Overriding tokens goes furthest, and survives changes to trail's own CSS:

:root {
  --accent: #0f766e;
  --card-shadow: none;
}
:root[data-theme="dark"] { --accent: #5eead4; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) { --accent: #5eead4; }
}

Beyond that it is ordinary CSS against trail's own class names — .card, .sidebar, .admonition, .code-block and the rest. Those class names are not a stable interface: they are what the compiled templates happen to emit today, and a trail upgrade may change them. Token overrides are the durable half.

Injecting into the head #

For anything that is not a stylesheet — a webfont link, an analytics snippet, extra meta tags — head_html names a file injected verbatim at the end of every page's <head>.

What you cannot change #

  • The templates. They are compiled into the binary and are not user-replaceable. Page structure is trail's.
  • The layout, beyond what CSS can reach.
  • The set of page types. There is no way to add a new kind of page.

That is the trade trail makes: no plugin system and no theme directory, in exchange for a single binary with nothing to install and one appearance to maintain.

Edit this page