Marrow

ADR-0013 — Design token contract and theme overrides

Replace hardcoded CSS palettes and edge stroke widths with a unified design token contract, CSS custom properties, and validated config schema.

Status: accepted

Compiler stylesheets in @marrow/site and @marrow/runtime previously hardcoded palette values and link stroke metrics, preventing enterprise customization. Marrow now exposes a unified design token contract with CSS custom properties, dynamic Canvas 2D synchronization, and validated theme overrides in marrow.config.yaml.

Why

Hardcoded palette hex codes and edge stroke widths locked diagram styling to fixed defaults. Users could not match company design systems or customize stroke weights for dark and light presentations without maintaining a fork of the compiler.

Raw CSS stylesheet injection was considered and rejected. Injecting unparsed user CSS introduces injection vulnerabilities, causes CSS selector collisions, and fails to synchronize with the Canvas 2D rendering engine.

Centralizing tokens in @marrow/site (tokens.ts) establishes a single contract. Palettes, status indicators (up, down, degraded), and physical link stroke widths map to CSS custom properties (--bg, --panel, --edge-width-*). The configuration parser validates color entries with a strict regex that rejects injection payloads, while link stroke widths are validated as positive finite numbers.

During active development with npm run dev, @marrow/server watches marrow.config.yaml for edits. The compiler re-evaluates tokens and hot-reloads connected browser tabs live without requiring process restarts.

Consequences

Single contract across renderers. Token overrides apply identically across SVG DOM elements, tooltips, inspector drawers, and the HTML5 Canvas 2D engine.

Injection safety. Color strings are strictly validated before injection into the document <head>, preventing stylesheet injection.

Live reload ergonomics. Theme edits in marrow.config.yaml update live diagrams immediately through Server-Sent Events.

Considered and rejected

Arbitrary CSS injection. Rejected because raw CSS bypasses the Canvas 2D engine and introduces style injection vulnerabilities.

Per-element inline style attributes. Rejected because inlining CSS properties on every SVG element inflates standalone bundle sizes and breaks theme switching.