- 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:
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.
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
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.
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.
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:
Base components built on Radix primitives with standard styling and behavior.
Examples: Button, Input, Select, Checkbox, Radio, Dialog, Dropdown
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