0013. Theme system and design tokens
Status
Section titled “Status”Accepted
2026-10-25
Context
Section titled “Context”Customization is one of Astela’s core principles (principles). Users choose light, dark or system appearance and an accent color, import and export theme files, edit themes with a live preview and give each workspace its own theme. At the same time the interface has to stay readable and consistent: every built-in surface must meet WCAG 2.2 AA, and the generated screenshot gallery is reviewed for visual quality.
The browser UI is spread over several renderers: one shell view per window, a lazily created overlay view for menus and prompts, and internal pages such as app://settings (ADR 0008). A theme change has to reach all of them at once. A workspace switch must not flicker, and a change in the theme editor must be visible in under 100 ms.
Decision
Section titled “Decision”Design tokens are the only source of visual values, and themes are data files that override tokens.
- Tokens live in
app/src/ui/ui-kit/tokens/and cover colors, spacing, radii, borders and motion. Components read them through CSS custom properties and contain no literal colors or sizes. - Colors are defined in OKLCH. The brand palette has an accent, an 11-step neutral scale and status colors; its final values will be kept in
assets/brand/palette.jsonnext to the logo once the brand assets are added (ADR 0024). - Theme files (
*.theme.json) contain$schema,id,name,version,base(lightordark),tokensand an optionalnewTabbackground. The tokens cover accent, surfaces, text, borders, focus, tab and toolbar specifics, and the radius (0 to 6 px). Themes contain no gradients, transparency or blur, in line with the design rules. The format is a Valibot schema with a generated JSON Schema (ADR 0007). User themes are stored asthemes/*.theme.jsonin the profile (data). - Theme engine: the
IThemeServicein the browser process resolves a theme, derives hover, pressed and disabled states in OKLCH and checks contrast against WCAG 2.2 AA (at least 4.5:1 for text, 3:1 for UI elements). It applies the resulting custom properties to the shell, the overlay and internal pages in the same frame. - Import and export: imported files are validated against the schema and take effect immediately. Export writes a canonical file, so exporting and re-importing a theme is byte-identical.
- Modes: light, dark or system through
nativeTheme. The accent comes from a preset, a free color, or the system accent on Windows and macOS. - Scopes: a theme applies globally or to one workspace. Switching workspaces only swaps tokens and completes within one frame.
- Density and fonts are settings (ADR 0011) that act through tokens. Density has three steps on a 4 px grid: compact (toolbar 32 px, tab 28 px), normal (40 px, 34 px) and comfortable (48 px, 40 px). Click targets are at least 24 × 24 px everywhere.
- Motion lasts at most 150 ms, animates only
transformandopacity, and is switched off underprefers-reduced-motion. - Built-in themes: Light, Dark and three original themes, all contrast-checked.
- Theme editor (
app://theme-editor): a token list for colors and radius with live preview on the real window, a contrast display that warns below 4.5:1, file import and export, and duplication of built-in themes. Background images and a local theme gallery follow in later milestones (roadmap). - The safe mode (
--safe-mode) starts without user themes.
Acceptance is automated where possible: contrast and axe checks, plus screenshot diffs of all four tab bar positions in light and dark on every pull request (ADR 0022). Maintainers review the screenshot gallery once per minor release. The details are described in settings, policies and themes and in the design system.
Consequences
Section titled “Consequences”Positive:
- One token change is expected to update every surface consistently, which keeps restyling cheap.
- Themes are small, portable, schema-validated files that cannot inject code or arbitrary CSS into privileged pages.
- Derived states and contrast checks keep custom themes readable; the editor warns before a user saves an unreadable theme.
- Workspace switches stay instant because they only exchange custom properties.
- Density, fonts and accent use the same mechanism as every other setting, so policies can preset or lock them.
Negative:
- Token names become a public format. Renaming or removing a token needs a theme schema version and a migration, like any other stored data.
- Themes cannot change layout or arbitrary styles; deeper restyling requires new tokens.
- OKLCH derivation and contrast checking are project code that needs tests against every built-in theme.
- Applying tokens to several renderers in one frame needs coordination in the browser process.
Alternatives considered
Section titled “Alternatives considered”- User CSS files in the style of
userChrome.css: maximum freedom, but they break with every UI change, cannot be checked for contrast and widen the attack surface of privileged pages. - Chrome-style theme manifests: only a few colors and images; not enough for density, radius or per-workspace themes.
- HSL or sRGB color math: lightness steps are perceptually uneven, so derived hover and disabled states would need manual tuning per theme.
- CSS-in-JS or a utility CSS framework: adds runtime or build complexity without improving theming; custom properties already inherit into the shadow roots of Lit components (ADR 0005).
- One stylesheet per theme: every theme would duplicate component styles, and themes would drift apart over time.