Adding a theme¶
A theme is a flat map of CSS custom-property values. Pluma ships two (pluma-teal, pluma-rose). Adding a third is a one-file change.
File¶
Create web/src/themes/my-theme.ts. Mirror the shape of pluma-rose.ts:
import type { Theme } from './index'
export const myTheme: Theme = {
id: 'my-theme',
name: 'My Theme',
vars: {
// Neutral scale (darkest → lightest). RGB triples; no rgb() wrapper.
'--ink-950': '12 14 20',
'--ink-900': '20 24 32',
'--ink-850': '28 34 44',
'--ink-800': '40 48 60',
'--ink-700': '64 76 92',
'--ink-600': '96 112 132',
'--ink-500': '128 148 168',
'--ink-400': '160 176 192',
'--ink-300': '188 202 218',
'--ink-200': '212 224 236',
'--ink-100': '232 240 248',
'--ink-50': '244 248 252',
// Brand accent. Three flavours.
'--accent': '139 92 246', // your primary
'--accent-soft': '139 92 246', // same RGB, used with /xx alpha
'--accent-bright': '167 139 250', // brighter cue for "live" badges
// Speech / prose accent (in-chat dialogue colour).
'--speech-accent': '245 232 199',
// Semantic. Single hue per concept; alpha at the use site.
'--danger': '248 113 113',
'--success': '52 211 153',
'--warning': '251 191 36',
'--scrollbar-thumb': '167 139 250',
},
}
Register¶
web/src/main.ts:
import { plumaTeal } from './themes/pluma-teal'
import { plumaRose } from './themes/pluma-rose'
import { myTheme } from './themes/my-theme'
registerTheme(plumaTeal)
registerTheme(plumaRose)
registerTheme(myTheme)
The theme picker (Settings → General → Theme) reads listThemes() at render time, so your theme shows up in the radio group automatically with a split surface/accent swatch.
Tokens¶
All colour tokens stored as raw R G B triples (space-separated, no rgb() wrapper). Tailwind's <alpha-value> placeholder works at the use site:
For the full token list with use-sites, see Reference → Theme tokens.
Naming convention¶
Themes follow <product>-<adjective>: pluma-teal, pluma-rose. ink-* is the neutral-scale token prefix, not the theme name. So pluma-ocean is right, ink-ocean is wrong (the ink-* namespace is for token names).
Contrast guidance¶
The mid-band of the ink scale (ink-400 through ink-600) drives secondary text. Aim for ~4:1 contrast against ink-900 at ink-500. Below 2.7:1 the captions get hard to read on a desktop LCD even though they look fine on AMOLED phones.
Plugin axis (future)¶
When the shareable-themes feature lands (gated on the CSP + validator under smelt-sze), users will be able to drop theme JSONs into <datadir>/themes/ without recompiling. Token names will be allowlisted; values will be RGB-triple-validated. Until then, theme registration stays code-only.
The plugin axis itself (smelt-7bq) does NOT cover themes — themes are data, not code, and don't need the gRPC subprocess machinery.