Wick UI — a design system as a product
Turned duplicated, drifting UI across several products into one published, tested, accessible source of truth.
- 47+
- Components in production
- 500+
- Commits
- 56 / 51
- Test suites / Storybook stories
React · TypeScript · Radix UI · Tailwind CSS · CSS Modules · CVA · Storybook · Vitest · Playwright
Wick UI is QuestionPro’s published React + TypeScript design system. I work on it as a core engineer — building components, their tests, and the tooling around them end-to-end, and partnering daily with UX, backend, and product to keep the system aligned with how the products actually ship.
Context
QuestionPro ships several products that each render the same kinds of UI — forms, tables, menus, dialogs. Each product team had rebuilt those pieces independently, so the same button existed many times over with subtly different behaviour.
Problem
There was no single source of truth for UI. Component code was duplicated across apps, UX drifted between products, and accessibility was applied inconsistently — each team re-solving keyboard, focus, and RTL behaviour from scratch, or not at all.
Constraints
- Multiple existing products had to adopt incrementally — a big-bang rewrite was off the table.
- Accessibility was a baseline requirement, not a nice-to-have — WCAG semantics, keyboard support, and full RTL.
- Published to npm and consumed by other teams, so releases had to be versioned, tree-shakeable, and non-breaking.
Decisions
-
Build on headless primitives (Radix UI, TanStack Table) rather than hand-rolling behaviour.
Why Correct keyboard, focus, and ARIA semantics are the expensive, error-prone part. Owning the markup and styling while delegating behaviour to audited primitives bought correctness without slowing delivery.
-
Style with Tailwind plus CSS Modules and design tokens, with CVA for variants.
Why Tokens keep theming consistent; CVA makes variant APIs typed and predictable; CSS Modules contain the styles a consumer can't accidentally override.
-
Treat tests and Storybook as part of "done", not a follow-up.
Why A component library is only trustworthy if its behaviour is pinned. Vitest + Testing Library for logic, Playwright for interaction, Storybook as living documentation.
-
Keep the public surface lean — minimal, predictable props and minimal CSS, tuned for performance.
Why A small, consistent prop API is easy to learn and hard to misuse; less CSS and fewer re-renders keep components fast on data-heavy screens.
Trade-offs
-
Headless primitives over Hand-built components
Accessibility correctness and long-term maintenance outweighed the appeal of zero dependencies.
-
A typed, constrained prop API over Maximum flexibility
A component that's hard to misuse is worth more to a platform team than one that can do anything.
Results
- 47+ accessible, production-ready components adopted across multiple products.
- 12+ core components shipped end-to-end — Select, Combobox, Content Editor, Modal, Toast, Date & Time Picker, Calendar, Segmented Control, App Header, Popover, Chip, Pagination.
- Built the complex interactive pieces — Select and Combobox — with async data, keyboard navigation, filtering, and accessible listbox semantics, behind a lean predictable API.
- 56 test suites and 51 Storybook stories covering behaviour and documentation.
- A reusable React Rich Text Editor published as its own npm package, now powering content editing across internal products.
- Earlier, on the analytics (BI) track: built interactive survey-analytics dashboards with React Grid Layout and chart libraries, tuning rendering for large datasets.
Lessons
- A component isn't done when it works — it's done when another engineer can adopt it without reading the source.
- Accessibility is far cheaper built in than retrofitted; headless primitives make "built in" the default path.
- The API surface, not the rendering, is the part you'll live with for years.