• Radix UI
  • React Hook Form
  • Design System

Building a Unified Component Library

Creating a scalable design system to solve design inconsistency and accelerate form-heavy development across multiple PrimaryBid applications

Company
PrimaryBid
Role
Senior React Engineer
Scope
Multiple Applications
  • 5+ Apps

    Unified across platform

  • Accessible

    Built on Radix primitives

  • Faster

    Development velocity

Context

The Problem

PrimaryBid operates multiple applications serving different user types—administrators, brokers, retail investors, and more. Each application contained specific, detailed multi-step forms critical to the business.

Over time, the company faced two major challenges:

Design Inconsistency

Forms across different applications looked and behaved differently. There was no unified design language, creating a fragmented user experience.

Each team was essentially reinventing the wheel, leading to duplicated effort and inconsistent quality.

Technical Fragmentation

Different projects used different styling approaches—some used CSS Modules, others used Emotion. There was no consistency.

Form implementations varied wildly. Some used React Hook Form, others had custom solutions, making maintenance a nightmare.

Constraints

The Challenge

Build a component library that would:

  • Unify design across all PrimaryBid applications
  • Be accessible by default (critical for financial applications)
  • Speed up form-heavy development
  • Remain flexible enough for customization (including use in the IPO app)
  • Establish clear technical standards (styling approach, form handling)

Engineering

Technical Decisions

Foundation: Radix UI

After considering accessibility requirements, we quickly opted for Radix UI. The decision was straightforward:

  • Unstyled, accessible primitives out of the box
  • ARIA compliance handled automatically
  • Keyboard navigation built-in
  • Full control over styling while getting accessibility for free

For a fintech platform where accessibility isn't optional, Radix was the clear choice.

Forms: React Hook Form as Standard

Our stack was already mainly using React Hook Form, so we standardized on it completely. The library needed to expose a unified solution to speed up form building.

This meant creating form components that wrapped Radix primitives with React Hook Form integration, making it trivial for developers to build complex forms quickly.

Styling: CSS Modules with Variables

We defined CSS Modules as the standard to eliminate the fragmentation between CSS Modules and Emotion across projects.

The approach used 2 CSS variables for each property:

  • Default variable: The library's default styling
  • Custom variable: Project-specific overrides that would work in any application

This meant components looked consistent by default but could be customized without fighting the library.

Solution

The Architecture

Two-Layer Approach

I built the component library in two distinct layers:

Layer 1: Primitive Library

Base components built on Radix primitives with standard styling and behavior.

Examples: Button, Input, Select, Checkbox, Radio, Dialog, Dropdown

Layer 2: Form Component Library

Form-specific components that integrated React Hook Form with the primitives.

Examples: FormInput, FormSelect, FormCheckbox with built-in validation, error handling, and labels

Key Requirement

Components needed to remain customizable. We were planning to use this library in the IPO app too, which had specific design requirements. The CSS variable approach made this possible.

Key work

Implementation Highlights

Developer Experience First

The library was designed to make the right thing easy:

  • Form components handled validation messages automatically
  • Error states were styled consistently without extra code
  • TypeScript types ensured correct usage
  • Default behaviors were sensible but overridable

Accessibility Built-In

By building on Radix, accessibility came for free:

  • Screen reader support out of the box
  • Proper ARIA attributes without developer effort
  • Keyboard navigation that just worked
  • Focus management handled correctly

Customization Through Variables

Each project could override the custom CSS variables to match their brand while maintaining consistent behavior and structure. This meant the IPO app could have its unique look while using the same robust components.

Outcome

The Impact

From 6-week sprint to enterprise platform:

  • Unified 5+ applications with consistent design language
  • Dramatically improved development velocity for form-heavy features
  • Accessibility by default across all applications
  • Eliminated technical fragmentation with clear standards (CSS Modules, React Hook Form)
  • Flexible enough to be used in both internal tools and customer-facing IPO app

Reflection

Lessons Learned

Standardize early, but leave room for customization.

The CSS variable approach let us enforce consistency while acknowledging that different products have different needs.

Developer experience drives adoption.

The library succeeded because it made developers' lives easier, not because it was mandated. When using the library is faster than not using it, adoption happens naturally.

Build on solid foundations.

Choosing Radix meant we got accessibility for free and could focus on the design system layer rather than reinventing primitives.

Layer your abstractions.

The two-layer approach (primitives + form components) gave teams flexibility—use the form components for speed, or drop down to primitives for complex custom cases.

Stack

Technologies Used

ReactTypeScriptRadix UIReact Hook FormCSS ModulesZodStorybook

More Case Studies