Skip to content
Alek Walker
The @prudentia/ui Storybook: token-driven components with light/dark themes and accessibility checks built in
All work

Design Systems · Accessibility

Authoring a Production Design System From Zero

With no dedicated design-systems team to build one, I sole-authored a versioned, token-based component library now backing a four-codebase ecosystem, taking it from zero to a published package in about 3 months with 68 components, WCAG AA, and a 90% coverage gate built into CI.

Company
Prudentia Sciences
Role
Senior → Principal Product Designer
Timeline
Mar 2025 – present
Version
v0.27.0

Context

Prudentia's product surface had grown into two end-user apps and two shared feature libraries, each built by whichever team touched it first. There was no dedicated design-systems team to own consistency across them, and none was getting funded. I took it on myself, alongside my own shipping product work, and built the thing the org needed but hadn't staffed.

Zero to a published, versioned package.

3mo

From zero to first release

68

Components shipped

AA

WCAG conformance

90%

Coverage gate on every merge

Accessibility checks run in CI, not in review.

Problem

Multiple front-ends were each re-implementing their own overlays, tables, and theming. There was no shared token contract, no accessibility floor, and no release discipline, and no team whose job it was to impose one. At a life-sciences company, that inconsistency isn't just visual noise: the same evaluation workflow could look and behave differently depending on which app a scientist happened to be in, which is exactly the kind of drift that erodes trust in regulator-facing tools. The mandate I set for myself: build a single source of truth that teams would actually adopt, authored alongside shipping product work rather than as a separate initiative someone had to wait on.

Process

Architecture first: product code never touches a raw palette value. It consumes intent, and the system resolves the rest, which is what makes theming and accessibility solvable in one place instead of in every app.

1

Tier 1 · Primitives

Raw palette, generated from Figma, never hand-edited

2

Tier 2 · Semantic tokens

Intent-based, each with a subtle / subtlest / hovered / pressed ramp

branddangerwarningsuccessinfo
3

Tier 3 · Tailwind @theme map

Clean utility-facing names, one indirection from the semantic layer

--color-brand--color-danger--color-surface

a11y override layer

A hand-authored contrast layer sits after the Figma export, so a token re-export can't silently regress it. Ships WCAG 2.1 AA, with AAA where the palette allows.

Dark mode = one remap

A single theme class remaps Tier 2 only. Because components consume Tier 3, the whole system flips with zero per-component work anywhere else in the tree.

Tier 2, the semantic layer: each intent gets a hovered, pressed, subtle, and subtlest state ramp, not a color a component picks for itself.

Default

Hovered

Pressed

Subtle

Subtlest

brand

danger

warning

success

info

The same tokens drive every interactive state, so a primary action, a secondary action, and an icon button all stay in sync by construction instead of by convention.

Default

Hover

Pressed

Focus

Disabled

Primary

SaveSaveSaveSaveSave

Secondary

CancelCancelCancelCancelCancel

Icon

Governance without a governance team. Adoption, not architecture, was the real risk. I gave the design-system repo its own Claude instruction file, written to operate like a seasoned UX/UI developer: WCAG AA as table stakes on every contribution, not a nice-to-have. Migration ran the same way, built into the normal rhythm of work rather than a dedicated sprint: hand-rolled components and hardcoded hex values got swapped for the system's tokens whenever a developer touched that code for product work, so the legacy surface shrank gradually instead of blocking on a big-bang rewrite.

Governance had to be built, not just tooling. I stood up recurring design-system rituals and review channels that gave other designers and engineers a place to propose changes and align on adoption before it landed.

All checks passed

5 / 5
conventional-commitsPR compliance
codeownersReview routed
chromaticVisual review
coverage ≥ 90/90/80/9070 test files
release-pleaseSemver, changelog, publish

Rituals and review channels are where people agree on a change before it reaches this gate.

Motion as a token, not a per-component decision.

The same principle as color, applied to movement: duration and easing live as tokens, not per-component decisions, aliased through the theme layer so one default sets the system's tempo everywhere at once. The tokens below are shown in motion, not just listed.

Duration, a 3-step tempo scale

fast · 120ms
base · 180ms
slow · 250ms

Easing, a directional pair

ease-enter · decelerate (fast start, settles in)

ease-exit · accelerate (slow start, speeds away)

Reduced motion, a library-wide kill-switch

A single prefers-reduced-motion rule neutralizes every animation and transition centrally, so no individual component can forget to respect it.

Coverage isn't complete yet: one composite animation token exists so far, a brand-mark toggle flourish, and the reduced-motion switch currently also freezes looping loaders, a known trade-off the code calls out directly rather than leaves silent, with a scoped opt-out planned next.

Solution

A versioned, auto-released component library published to a private registry, built on Tailwind v4's @theme, and Storybook with axe-core in CI so a violation fails the build instead of shipping. Radix primitives bridged early accessibility needs, but the goal was never to depend on it long-term: the plan is to move off Radix entirely as the system's own primitives mature. Dark mode is a single remap of the semantic tier, not a per-component fix: a full dark-mode correction elsewhere in the ecosystem landed in 2 files and 23 lines, because the semantic tokens absorbed the rest.

The Evaluations table and Risk-Adjusted Valuation chart shown in light and dark mode side by side, split down the middle

Before: 4 separate forks, each re-solving overlays and theming locally, with dark mode re-fixed screen by screen. One app's refactor onto the system, in a single PR:

111

Files moved onto the system

−562

Net lines removed

4

Duplicated overlay primitives deleted

refactor: consolidate overlays onto @prudentia/ui

Merged

Dialog.tsx

Deleted

Popover.tsx

Deleted

Tooltip.tsx

Deleted

ComboBox.tsx

Deleted
107 more files · imports moved to @prudentia/ui

Outcome

The design system is the substrate two feature libraries are built on, which then compose back into the apps.

Private registry
release-please publish

@prudentia/ui

v0.27.0 · 68 components

End-user app

Consumer

End-user app

Consumer

Feature-UI library

Peer-depends

Feature-UI library

Peer-depends

~250 source files import @prudentia/uiMost adopted: Button, Dialog, Tooltip

~1wk

repo to first release

260

commits

70

test files

15

Radix primitives wrapped

First component published within a week of the repo's creation. The package now sits at v0.27.0 and is the foundation of a four-codebase ecosystem: two end-user apps and two shared feature-UI libraries that peer-depend on it and compose back into those apps. Six front-end engineers build on it directly, and joined the same rituals that govern it, not just the code. Roughly 250 product source files import the system directly across the two apps alone, with Button, Dialog, and Tooltip the most broadly adopted primitives.

Across 260 commits and 70 test files, the org went from four separate teams each re-solving the same problems to one shared contract they build on instead of around, with noticeably less back-and-forth between design and engineering catching the inconsistencies a shared system now prevents by construction.