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):
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-indefault(only forpalette = "default") - wholesale: a complete
docs/tokens.cssshort-circuits everything — plasmoid emits it verbatim (copy the emitted/assets/tokens.cssas 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):
| Group | Tokens |
|---|---|
| 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-faceandwoff2under/assets/fonts/(with the license), so every project gets good typography with zero setup.font-display: swapmeans text paints immediately in a fallback and swaps in the web font when ready. - Bring your own. Drop
woff2files intodocs/fonts/namedbody,body-bold,body-italic,body-bolditalic(and the same formono), then setfont_body/font_monoto your family names. plasmoid wires@font-facefor 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.
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.