[Guide] Build a Design System Playground (Theme by Side Effect) - implementation notes
The Design System Playground guide is an opinionated take: themes get applied via render effects (so theme changes are treated like documented behavior), scope overlays decide defaults per surface, and the playground uses route state tabs so switching components feels like navigation, not local UI that disappears. The derived tokens output also seems like a good way to keep the UI consistent across panels.
How did you structure "theme by side effect" so it stayed observable and didn't turn into mysterious global CSS state? Did you store derived tokens (resolved palette/spacing/typography) and how did you keep them from drifting across components? What did your scope overlays look like in practice (product tiers, environments, per-route defaults)? What evidence/log lines were most useful when debugging "why is this component themed differently"?
Comments (18)
Back to latestTheme by side effect is fine as long as the effect has a name and produces evidence.
If the effect is anonymous, theme bugs become folklore.
We made the theme effect return a doc string and we log it as the contract line:
txt
[tips] renderEffect=theme:apply scope=brand:alpha mode=dark reason=user:toggle tokenRev=42
[tips] derive=tokens reason=theme:apply tokenRev=42 palette=alpha-dark
Then the UI renders data-scope, data-theme-mode, and data-token-rev. A screenshot tells the story.
Counterpoint: side-effect theming can become a performance problem if it triggers global recalculation too often.
We introduced a posture: theme changes are allowed only on explicit boundaries (tab switch, toggle), not on hover or incidental state.
Yes. Theme is a contract, and contracts have boundaries.
When theme changes are bounded, you can document and test them. When they're incidental, you're chasing ghosts.
Stored-derived tokens were worth it because multiple panels need the same resolved token values (preview, token table, inspector).
If each panel resolves tokens, they will disagree and the playground stops being authoritative.
Long-form: design systems are trust products too.
If the playground can't explain *why* a component looks the way it does (scope, mode, token rev), engineers will stop trusting it and style by copy/paste.
The guide's insistence on evidence keys is what makes the whole thing viable.
Scope overlays were the surprising win. We used them to model product tiers and environments. The overlay choice was part of the contract and we logged it:
txt
[tips] scopeOverlay set=tier:enterprise reason=route:enter
[tips] scopeOverlay set=env:staging reason=user:toggleWe rendered a compact token summary string as evidence: palette=alpha-dark spacing=v2 typography=brand.
It prevented long arguments about whether a component was "wrong" or just under a different scope.
Counterpoint: storing derived tokens can hide token resolution bugs if derive isn't centralized.
We required a single derive path and logged derive reasons every time. If token rev changes, you must be able to say why.
Yes. Derived state is only safe if it is deterministic and derived from one place.
Otherwise it becomes a second source of truth and design systems are allergic to that kind of ambiguity.
Route state tabs were great because it made the playground navigable and replayable.
If tabs are local UI state, people can't share a link to "that exact component under that exact scope".
Docs tie-in: the styling/theming posture + effects docs explain why naming and evidence matter here.
Related: Styling and Theming and API Reference.
Long-form counterpoint: theme-by-effect can encourage teams to stop thinking about CSS scoping and rely on "the effect will fix it".
We still needed a clear boundary for what tokens can do and what component styles must do.
Tokens are contract inputs, not a replacement for component-level discipline.
We treated theme application as an idempotent operation: apply theme repeatedly yields the same result.
That made it safer to "replay" theme on navigation without worrying about accumulation.
A pragmatic debugging win: show data-theme-source (scope overlay vs user toggle vs route default).
If you can see the source, you can fix the right layer instead of chasing CSS.
If you do scope overlays, you need governance for overlay precedence. We logged overlay resolution so precedence was explicit:
txt
[tips] scopeResolve winner=tier:enterprise loser=routeDefault reason=precedenceCounterpoint: the playground can become a second app with its own complexity.
We kept it small: preview, token table, and one inspector panel. The guide's route-tab approach helped keep it disciplined.
Long-form: the value isn't that theme is a side effect; it's that theme changes become documentable behavior.
Once behavior is documentable, it becomes testable and supportable. That's what design systems need.
We also added a dev-only overlay that prints the last 10 [tips] lines in UI.
It made theme bugs reproducible because you can see the contract story without opening the console.
If you implement this, start by choosing your evidence keys (scope, mode, token rev, source).
Once evidence is visible, the rest of the architecture decisions become much easier to validate.