Lab · Design system

Design System

One system behind four live products: this portfolio, Role Analyzer, the UX Design Briefing and the Kiro UX Auditor.

236Design tokens in the W3C design-token format, from primitives to components
31 → 10Type sizes after auditing about 5,936 CSS declarations
4Live products running on the one system as of October 8, 2026

Four products had drifted into four copies of the same look

Every change meant updating them separately. So I audited the CSS first, then built one source of truth that all four load.

Design tokens, type and components: coral and cyan swatches with token names, a serif and sans type specimen, and a primary button, ghost button and pillDesign tokens, type and components: coral and cyan swatches with token names, a serif and sans type specimen, and a primary button, ghost button and pill

What I did

I started with an audit, not a palette: about 5,936 CSS declarations, 31 type sizes and 2,007 copied component declarations across the four products.

From that I built 236 tokens in the W3C design-token format, layered from primitives to semantic roles to component values, with a zero-dependency build, a validator that checks WCAG 2.2 contrast, CI and a decision log. A matching Figma library mirrors it: 189 variables, 12 text styles and 7 components. Then I migrated all four products onto the tokens. In v0.2.0 I added shared components (button, card, pill, tag, stat, step, check item and links, plus a set of 12 icons), documented in Storybook. The portfolio uses everything except the link styles. In v0.4.0 I added a light theme, and the portfolio now follows the visitor's system setting with a switch in the header.

How it works

1

Audit what exists

Count what the four products actually used before deciding what the system should contain.

2

Define tokens in layers

Primitives, then semantic roles, then component values, so a change at one layer carries through the layers above it.

3

Build and validate

A zero-dependency build outputs one CSS file that each product loads from a pinned release. A validator checks WCAG 2.2 contrast.

4

Migrate and record

Move the products over, first the tokens and then the components, and write down each decision in DECISIONS.md so the reasoning outlives memory.

Decisions that matter

✓

31 type sizes cut to 10, with a 12px minimum text size.

✓

44px minimum touch targets.

✓

A border never changes a button’s height.

✓

Dark mode only for now.

✓

Every decision is recorded in the decision log, not left in my head.

✓

Link, tap and focus defaults sit in a lower CSS layer, so a component’s own styles always win over page-level rules. I found this when a page rule turned the primary button’s text the wrong color.

✓

Result: the portfolio’s shared CSS dropped from 2,445 to 1,915 lines, with about 1,740 duplicate declarations removed, and to 1,795 lines once it moved onto the shared components. The migration also fixed an unsafe way Role Analyzer displayed AI output.

Honest limits

Shared components arrived in v0.2.0, and only the portfolio uses them so far; it is also the only product with the light theme and the switch from v0.4.0. The other three products load the tokens and still keep their own copies of buttons and cards, and the header and footer are still copied between repos. Forms, tabs and tables are not built yet (forms are next, then tabs, tables and accordions). This is my own system across my own four products, not an organization adopting one.

Building a system across several products?