plasmoid
EN

Theming & fonts

plasmoid owns its CSS — a small, readable stylesheet driven by CSS custom properties, with light and dark modes built in. There is no Tailwind, no CSS framework, and no build step beyond plasmoid itself.

Light and dark

Colors are CSS variables with light and dark palettes. A reader’s choice is applied before first paint by a tiny inline script, so there is no flash of the wrong theme (FOUC). A toggle in the chrome switches and remembers the preference. With JavaScript disabled, the site honors the OS prefers-color-scheme.

Both palettes are checked for adequate text contrast.

Design tokens (tokens.css) — pick a tone, or override

The colour variables live in a generated /assets/tokens.css, linked before the chrome stylesheet. It carries the full palette (neutrals, accent, the signature gradient ramp --ramp-1…4 + --grad-main, button colours, semantic colours) for light and dark. The chrome — and any landing you author — consume these var(--…), so the whole site is themed from one file.

That file is assembled from a shared base layer (neutrals, semantics, elevation) plus one tone overlay (the hues, and any surfaces the tone tunes).

The built-in default

plasmoid is distributed software, so the binary ships exactly one tone — a plain, neutral, brand-agnostic default (tasteful grayscale surfaces + one calm accent, full light/dark). It looks good on a basic docs page out of the box, with zero configuration. That’s all most projects need.

[theme]
palette = "default"   # the built-in neutral tone (this is also the default if omitted)

Bring your own theme library (themes_dir)

Richer or branded tones live outside the binary, in a theme library directory you point plasmoid at. A library is just a folder with the same shape as your docs/: a tokens.base.css and a themes/<tone>.css for each tone.

[theme]
themes_dir = "../brand"      # relative to this docs dir (absolute paths also work)
palette = "electric-blue"    # resolved from <themes_dir>/themes/electric-blue.css

Or pass them on the command line (these override the config):

plasmoid build --themes-dir ../brand --palette electric-blue

A relative --themes-dir resolves against your current directory; a relative [theme] themes_dir resolves against the docs dir. The library is read at build time — no network, no @import, fully offline and reproducible.

Resolution order

For a given palette, plasmoid takes the first that exists:

  • base layer: docs/tokens.base.css<themes_dir>/tokens.base.css → built-in base
  • tone overlay: docs/themes/<palette>.css<themes_dir>/themes/<palette>.css → the built-in default (only for palette = "default")
  • wholesale: a complete docs/tokens.css short-circuits everything — plasmoid emits it verbatim (copy the emitted /assets/tokens.css as a starting point).

A palette that resolves nowhere is a build error that lists exactly where plasmoid looked — and reminds you to set themes_dir if you haven’t.

The token contract

A base + tone together must define every variable the chrome consumes. To author a custom tone (or a whole library), set all of these for light, and override what changes for dark (:root[data-theme="dark"] + a @media (prefers-color-scheme: dark) mirror):

GroupTokens
Surfaces & text--bg, --bg-alt, --bg-elev, --bg-code, --border, --fg, --fg-muted
Accents--accent, --accent-fg (text on an accent fill), --accent-2
Gradient ramp--ramp-1--ramp-4 (lightest → deepest)
Composed gradients--grad-main (4-stop, large surfaces), --grad-accent (2-stop)
Buttons (solid)--btn-bg, --btn-bg-hover, --btn-bg-active, --btn-fg, --btn-shadow, --btn-shadow-hover, --btn-shadow-soft
Effects--glow, --shadow
Semantic states--ok, --warn, --danger

By convention the base layer supplies the neutrals (surfaces/text), the elevation --shadow, and the semantic states; each tone supplies the accents, ramp, gradients, buttons, and --glow (and may re-tune the neutrals to suit its hue). Every tone uses the same token names, so it’s a drop-in — only the colours differ.

Because the landing links the same /assets/tokens.css, your landing and docs stay in lockstep whichever route you take.

Fonts

Fonts are self-hosted — no third-party CDN, no network at build time. plasmoid bundles an accessibility-first default and lets you override it:

[theme]
font_body = "Atkinson Hyperlegible"   # the bundled default
font_mono = "JetBrains Mono"
fonts = "self-hosted"                 # "self-hosted" (default) | "system"
  • Bundled default. Out of the box, body text is set in Atkinson Hyperlegible (Braille Institute, OFL) — a typeface designed for maximum legibility. plasmoid emits its @font-face and woff2 under /assets/fonts/ (with the license), so every project gets good typography with zero setup. font-display: swap means text paints immediately in a fallback and swaps in the web font when ready.
  • Bring your own. Drop woff2 files into docs/fonts/ named body, body-bold, body-italic, body-bolditalic (and the same for mono), then set font_body/ font_mono to your family names. plasmoid wires @font-face for them and skips the bundled default.
  • System only. Set fonts = "system" for the zero-byte option: no @font-face, just your families leading a robust system fallback stack.

Either way the families lead a system fallback stack, so text is readable even before (or without) any web font.

Code highlighting

Code blocks are highlighted at build time into CSS classes (not inline styles), so a single highlighted HTML output is themed for both light and dark by scoped stylesheets. No syntax-highlighting JavaScript ships to the browser.

```rust
fn main() {
    println!("highlighted at build time");
}
```

Accessibility baked in

The generated chrome uses semantic landmarks, a skip-to-content link that actually moves focus, visible focus outlines, aria-current on the active nav item, and a keyboard-navigable search combobox. Accessibility is part of the default output, not an add-on.

Last updated: 2026-06-26