Work / Case Study 01

Keploy

A design language system and frontend architecture for an open-source API testing platform.

Role UX Engineer
Timeline Jan 2024 — Mar 2025
Client Keploy
Team Solo designer-engineer, embedded with 4 core maintainers
Scope Design language system · Token pipeline · React component library · Test-run console
Stack
React 18TypeScriptStyle DictionaryRadix PrimitivesStorybookVitest + Playwright
Keploy — cover
Overview

Keploy records real API traffic and replays it as deterministic tests and mocks. The product surface had grown organically across a web console, a VS Code extension, and docs — three codebases, three visual dialects, no shared source of truth. I owned the design language system end-to-end: token pipeline, component library, and the redesign of the test-run console that sits on top of it.

The challenge

Developer tools earn trust through precision. When a diff viewer renders a flaky timestamp as a test failure, or two surfaces disagree about what "warning orange" means, users stop believing the tool — and an API testing platform lives or dies on believability.

The audit found 14 button variants, 9 shades of the brand orange, and three different table implementations — one of which locked up above ~2,000 rows. Contributors (this is an open-source project, so most frontend PRs come from strangers) had no way to know which pattern was canonical. The brief: one system rigorous enough that a first-time contributor ships on-brand UI by default, without a designer in the loop.

01 — Foundation

Tokens as the contract

Everything starts from a three-tier token architecture — primitives → semantic aliases → component tokens — authored once in JSON and compiled by Style Dictionary to CSS custom properties, a typed TypeScript map, and a Tailwind preset. No surface consumes a raw hex value; the semantic layer is the only public API, so retheming (including the dark-first console theme) is a data change, not a refactor.

The typed map matters more than it sounds: token names are string-literal types, so a contributor referencing a token that does not exist gets a compile error, not a silently wrong color in production.

tokens/semantic.json → generated types
// tokens compile to CSS vars + a literal-typed map
export const token = {
  'surface.raised':   'var(--kp-surface-raised)',
  'status.pass':      'var(--kp-status-pass)',
  'status.flaky':     'var(--kp-status-flaky)',
  'diff.added.bg':    'var(--kp-diff-added-bg)',
} as const satisfies TokenMap;

// ✗ token['status.sucess'] — compile error, not a wrong color
02 — Components

Headless core, styled shell

The library is 38 components in two layers: Radix primitives handle focus management, ARIA wiring, and collision-aware positioning; a thin styled shell binds them to the token system. Compound-component APIs keep composition in JSX rather than in prop soup — a test-run card is <TestRun> wrapping <TestRun.Status>, <TestRun.Diff>, <TestRun.Meta>, not a component with 30 props.

Every component ships with Storybook coverage, an interaction test, and a docs page generated from the same source. CI runs axe on every story; a PR that regresses contrast or keyboard reachability fails before review.

Component library — 38 components, Storybook coverage map
03 — The hard problem

Making replayed traffic legible

The core screen is a diff between a recorded API response and the replayed one. Raw JSON diffs are hostile: a 4,000-line payload with one meaningful change and forty noisy ones (timestamps, UUIDs, server nonces). The redesigned diff viewer classifies noise declaratively — fields matched by the project's noise config render de-emphasized and excluded from the pass/fail verdict, with a one-click affordance to promote a flaky field into the config.

Test runs routinely contain thousands of assertions, so the run table is virtualized (TanStack Virtual) with stable row heights and keyboard-first navigation: j/k row traversal, enter to expand a diff, and every state deep-linkable. A 10,000-row run scrolls at 60fps where the old table died at 2,000.

Diff viewer — noise classification and latency waterfall
04 — Adoption

Migration without a flag day

Rewrites stall; strangler migrations ship. Legacy screens got the token CSS variables injected at the root, so old components inherited the new palette immediately, and a codemod swapped the highest-traffic primitives (Button, Input, Table) in bulk. An ESLint rule flags raw hex values and legacy imports on every new PR, so the system tightens instead of eroding — the ratchet, not the review comment, guards consistency.

Outcome

Dummy numbers, real shape: the system cut frontend build time for new screens by roughly 60%, and contributor UI PRs stopped needing design review in the common case — the tokens, lint rules, and CI gates carry that review instead. The console redesign shipped with the v2.3 release and became the default surface for new users.

38
components in the system
100% Storybook + axe coverage
−62%
UI build time for new screens
measured across 6 releases
10k
rows at 60fps
virtualized run table (was ~2k)
0
raw hex values in app code
enforced by lint ratchet