[Deep Dive #10] Styling Tokens - discussion
Has anyone tried treating styling tokens as route-owned resources (not just CSS variables)? I'm tempted by the evidence/debugging story, but I'm worried it's just more moving parts.
The pitch: shell owns posture, screens consume stable token surfaces, and the UI renders token evidence so theme drift is obvious during migrations.
Where do you keep tokens (shell state, vault, build-time constants)? Do you render evidence like theme name/token set version/overrides into the DOM? If you allow runtime token mutation, what guardrail keeps it from getting unpredictable? And for migrations: do you version token sets, or do you do rename bridges and deprecations?
Comments (10)
Back to latestWhy would tokens be in route state?
Isn't styling just a CSS concern?
The point is predictability + debugging. If a theme is a route posture, it can be rendered and audited like other evidence.
It also helps when different parts of the app migrate at different times-token evidence tells you what's active.
Got it. I was imagining token evidence as user-facing UI.
If it's just in the shell/debug slot (and tests/screenshots), that feels way more reasonable.
On a larger team, route-owned tokens make sense when themes affect layout and composition (not just colors).
We keep a tokenSetVersion in the shell and require any override to write evidence explaining why.
Tokens as resources are nice because you can precompute derived style bundles and store them.
It avoids each component building class names differently and causing subtle mismatches across panels.
We type tokens as a stable surface and forbid ad hoc string keys.
When tokens are a contract, you can refactor theme internals without breaking consumers.
And if you render token evidence, you can detect mismatched token sets instantly.
We've had bugs that were "half the app is on old tokens" and evidence was the only fast way to see it.
Runtime token mutation still scares me a bit.
But forcing evidence for any mutation at least keeps it from being silent.
We hit this in prod: we renamed a token and broke one screen that had a local fallback value.
Once we moved token resolution into the shell and stored derived bundles, the class of bug went away.
This is also a surface discipline issue: if screens can invent token fallbacks, tokens stop being a contract.
Shell-owned resolution + evidence keeps it honest.
Example evidence pattern:
tsx
shell.write('tokenEvidence', { theme: 'dark', tokenSet: 'v2', overrides: ['radius.lg'] });Question: where do you draw the line between tokens and component variants?
We keep tokens for primitives and variants for semantics, but evidence gets messy if both are mutable.
We treat variants as derived evidence from tokens + route posture.
It sounds backwards, but it keeps the surface small: tokens are the contract, variants are computed/stored evidence.
Theme migrations were the only time token evidence felt non-negotiable for us.
Otherwise you just argue over screenshots and "it looks right on my machine".
Route-owned tokens got way more practical for us once we treated tokens as a *resolved bundle* with evidence, not a bag of variables. We store a resolved map (so consumers stop doing ad hoc fallbacks), and we render evidence that proves which set and overrides produced it.
ts
type TokenSet = {
version: string;
tokens: Record<string, string | number>;
};
function resolveTokens(base: TokenSet, overrides: Record<string, string | number>) {
return { version: base.version, tokens: { ...base.tokens, ...overrides } };
}
function applyTokens(doc: any, base: TokenSet, overrides: Record<string, string | number>) {
const resolved = resolveTokens(base, overrides);
doc.write('resolvedTokens', resolved.tokens);
doc.write('tokenEvidence', { version: resolved.version, overrideKeys: Object.keys(overrides) });
}
Once resolvedTokens is the surface, consumers stop guessing and your UI stops drifting between panels.
I still avoid runtime token mutation in most apps. It turns styling into state management.
But the evidence rule makes it tolerable: if you're mutating tokens, you have to render exactly what you changed and why.