Associated with Testlify
In progress

Testlify - Shared Component Library

Led the initiative to introduce a shared Vue component library across Testlify's 4 applications, published as private packages, removing per-app UI duplication.

Testlify operates as a suite of 4 separate web applications. Previously, each product team developed and styled common UI components independently, causing duplicate engineering effort and visual inconsistencies across products. I proposed the idea and led the team that built a shared Vue 3 component library to standardize UI development.

What I built

  • Architectural consolidation: Led the initiative to consolidate common UI primitives into a centralized library, establishing consistent design tokens and component contracts across teams.
  • Stateless component design: Kept library components strictly stateless and presentational, allowing them to integrate cleanly into any of the 4 applications while keeping business logic decoupled.
  • Private package distribution: Published and maintained the library as versioned packages on GitHub Packages, enabling teams to adopt bug fixes and new components via standard dependency updates without code duplication.

Why it mattered

Centralizing UI development in a shared package eliminated redundant code across 4 applications, accelerated feature delivery cycles, and ensured a cohesive design system across Testlify's entire product suite.

Led, Not Solo

I proposed the library and drove the architecture, but this wasn't a one-person build: 86 commits from 7 contributors since May 2026, with my own commits making up roughly two-thirds of the total. Getting a shared design system adopted across 4 product teams needs more than writing the components; it needs the release process that makes adopting them low-risk, which became the actual hard problem.

Testing an Unreleased Component Change Without Publishing on Every PR

A shared library's biggest adoption blocker isn't writing the component, it's the gap between "I changed something" and "a consuming team can actually try it." Publishing a temporary version on every single PR would be real, recurring CI cost for a package most PRs never get installed anywhere, so it's opt-in: a PR gets a needs-snapshot label only when a consumer actually needs to test it, which triggers a snapshot.yml GitHub Action that publishes a temporary @pr<number> tagged package. A paired cleanup-snapshot.yml removes the tag once the PR closes. Regular releases go through Changesets on two channels, @latest from main and @beta from a staging branch, and the release workflow auto-merges its own generated "Version Packages" PR once CI passes.

A Monorepo That Enforces Its Own Dependency Direction

The library is split into layers, not one flat package: tokens, icons, and styles sit at the bottom with zero internal dependencies, core builds on those, ds builds on core, and ds-patterns builds on ds, core, and styles. Each layer can only depend downward, never up, so a low-level primitive can't quietly grow a dependency on a higher-level pattern component. ds-patterns isn't just generic primitives either: it includes actual Testlify domain widgets (a CandidateCard, for one), built on the same primitive layer everything else uses. Storybook, with the accessibility addon enabled, documents every component and is deployed publicly, so a consumer team (or an outside reviewer) can check a component's states and a11y results without pulling the repo. Every component also carries a dedicated .a11y.spec.ts file running real axe-core checks in CI, not just a Storybook addon for manual inspection.

Data Model & Scale

  • 6 published packages (ds, -core, -icons, -patterns, -styles, -tokens), each independently versioned.
  • 243 spec files (Vitest + Vue Test Utils) exercising the library directly, not just its consuming apps.
  • 9 GitHub Actions workflows covering CI, preview and production deploys, Storybook deploys, staging and production releases, PR snapshots, and snapshot cleanup.
  • pnpm workspaces plus Turborepo cache and scope the build task to packages actually affected by a given change; tests run directly via Vitest across the whole workspace rather than through Turbo's task graph.

Adoption rolled out per team, not on a forced flag day: the recruiter/admin app runs on all 6 published packages today, while the other product teams pull individual components onto their own release schedule rather than a single coordinated cutover across all 4 applications at once.

Build vs. Buy

No hand-rolled version-bump scripts: Changesets manages versioning and changelogs across all 6 packages from one changeset file per PR. No self-hosted private registry: GitHub Packages reuses the same GitHub auth that already gates repo access, instead of running and securing a separate registry like Verdaccio.

Stack

Vue 3, TypeScript, Vite, pnpm workspaces with Turborepo, Storybook with the accessibility addon, Changesets for versioning, Vitest and Vue Test Utils, published via GitHub Packages.