Skip to content

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:

<div class="bg-accent/15"></div>     <!-- background-color: rgb(20 184 166 / 0.15) -->

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.