Wonderschool · Design Systems

Making the design system visible

Objective

Turn invisible component drift into a diagnostic tool the whole team can see, agree on, and act from.

My Role

Systems Audit & Analysis

Tooling & Storybook Design

Migration Planning

Stack

Phoenix LiveView + React

Storybook

Chromatic for Visual Regression

Years in the making

None of this drift happened overnight, it was years of accumulated tech debt finally coming due. Components had scattered across the codebase with nobody tracking where they lived or whether a "new" one quietly duplicated an old one. Semantic tokens and primitive values got used interchangeably depending on who wrote the code and when, so the same visual intent could resolve to three different hex values in three different files. Government-specific themes, built for state partnerships like New Mexico's, got applied willy-nilly wherever a deadline demanded it, with no system for when a theme override was appropriate versus when it should have been a real token. The result: no consistent look and feel across our four different platforms, React web, LiveView web, iOS, and Android, each drifting further from the others with every release.

The spark

It started with a small, honest question: "Why does our data_table look different from table in Storybook? They should be the same." Pulling that thread revealed they were never the same component. They were three separate tables, data_table, table, and a newer table_new, each hand-built at a different time with different styling. Nothing in the code kept them in sync.

That one question exposed a bigger truth: on the LiveView side of our product, components had quietly drifted apart and nobody could see it, because each component lived alone on its own page or buried inside a single product screen. There was no place to line them up side by side.

Two systems, two speeds

Wonderschool builds UI on two stacks that share the same visual design tokens (colors, spacing, type) but maintain their components very differently.

React · healthy

~82 stories, ~98% coverage. One consolidated library, public Storybook, Chromatic visual-regression, Figma links, and accessibility checks. It avoided drift by consolidating early.

LiveView · drifting

~12 stories. Two parallel component sets: an auto-loading legacy set and an intentional one you must import by hand. Pages silently fall back to the old components, drift was the default behavior, not a one-off mistake.

What we built

We turned the LiveView Storybook into a diagnostic tool, not just a component showroom, two new pages, now in an open pull request.

27
blessed components
16
invisible, no Storybook story
3
separate table implementations
17
legacy components still in use
~11
per-app copies of headers & nav
309
files using the legacy icon

1. Component Audit: a census of the system

At a glance: what components exist, where each lives in the code, whether it has a story yet, and where the same UI has been built more than once. It surfaced the duplicate tables, three copies of the form component, a set of duplicate "Endor" components, and the legacy usage counts above. Every stat box is clickable, a number like "16 invisible" drills straight into the list it represents.

2. Drift Gallery: the visual proof

It renders each deprecated component directly beside its blessed replacement, old button vs. new button, old input vs. new input, old table vs. new table, so the divergence is something you see, not just read. Where a component genuinely can't be shown in isolation (it needs a logged-in user, a live query, or app context) we listed it honestly with the reason rather than faking it.

Button
Deprecated · CoreComponents
Save
Blessed · DesignSystem
Save
Text input
Deprecated
Email
Blessed
Email
you@example.com
Table
Deprecated
NameRole
Alice JohnsonAdmin
Bob SmithEditor
Blessed
NameRole
Alice JohnsonAdmin
Bob SmithEditor
Modal
DesignSystem.modal, click to open
Open blessed modal

The deprecated CoreComponents.modal and slim_modal aren't shown live here: they read undeclared internal state and only render correctly inside a real app page. That fragility is itself a reason they're deprecated.

Duplicate: pagination
Endor.Pagination vs CoreWeb.Pagination
Two pagination components for the same job (showing page 2 of 5)
Deprecated · CoreComponents
Showing 11 to 20 of 42 entries
1 2 3 4 5
Blessed · DesignSystem
Showing 11–20 of 42 records
1 2 3 4 5
The Drift Gallery (recreated here): deprecated components rendered live beside their blessed replacements, plus a real example of the duplication problem, two pagination components built for the same job.

3. Token documentation: making the invisible layer legible

Underneath every component sits a three-layer token system, primitive values, semantic tokens, and component tokens, that most of the team had never actually seen laid out. I built a documentation page that traces one color, blue, through all three layers side by side, then lists the full primitive palette and every semantic token (background, text, border, icon, button state) with its hex value and where it's used. Both the React and LiveView Storybooks read from this same source, so the page closes the loop the whole audit was about: one shared vocabulary, visible to everyone, not just the two people who happened to write it.

Color tokens, in three layers
One blue, traced through all three
Primitive
blue.600
Semantic
bg.action.primary
Component
button.bg.primary
The primitive palette
Semantic tokens
bg.action.primary
bg.status.success
bg.status.danger
The token documentation page (recreated here): one blue, traced through primitive, semantic, and component layers, with the full primitive palette and semantic token list beneath.

Where this goes next

The audit turns a vague sense of "our components are inconsistent" into a concrete, prioritized worklist: build stories for the 16 invisible components, consolidate the duplicate tables, forms, and Endor parallels onto a single blessed version, and retire the legacy set in order of effort, small contained orphans first, the 309-file icon campaign last.

The React Storybook is the model for where LiveView should land: public deployment, visual-regression, Figma links, accessibility checks. Because both stacks already share the same tokens, consolidating each side moves us toward genuine cross-stack consistency.

Why it matters

Drift becomes visible, and therefore preventable, you can't fix what you can't see. It creates a shared map designers, PMs, and engineers can agree on. It turns "our UI feels inconsistent" into a ranked list with real usage numbers behind it. And it compounds: every story added and every duplicate retired makes the next inconsistency easier to spot. What began as one puzzling table is now a durable, shareable tool for keeping the entire LiveView design system honest.

Back to home