From UI Fragmentation to a Shared Frontend Platform
Building a scalable Angular design system for three banking products
My contribution
- Designed the shared library architecture
- Established the Nx workspace structure
- Implemented 30+ reusable components
- Configured Storybook
- Defined contribution and component-boundary rules
- Worked closely with the designer on component variants, tokens, and edge cases
- Established the separation between shared UI and product-specific functionality
The challenge
The team was building multiple banking products that shared many of the same interface patterns.
However, there was no common UI foundation.
Similar components were being implemented independently across products, which created noticeable inconsistencies between pages and applications.
A button, input, calendar, or other interaction pattern could look or behave differently depending on where it appeared.
At the same time, duplicated implementation meant that maintaining the UI became increasingly expensive.
The problem wasn't simply visual inconsistency.
It was also an engineering problem:
The same UI problems were being solved repeatedly by different parts of the team.
This slowed development and made changes harder to maintain.
The goal was therefore not simply to create a collection of reusable components.
It was to establish a shared frontend foundation that three products could depend on while keeping product-specific business logic out of the shared layer.
Starting from zero
The design system was new both from a design and implementation perspective.
The designer was creating the visual language while I was responsible for turning that language into an architecture that could be consumed by multiple Angular applications.
One of the first decisions was therefore to avoid treating every component as an isolated implementation problem.
Instead, I looked at the underlying mechanics.
For interaction-heavy components such as:
- Inputs
- Buttons
- Calendars
- Form controls
I chose to build on top of PrimeNG 19.
PrimeNG already provided much of the difficult underlying behavior, including interaction mechanics and accessibility.
Rather than rebuilding those foundations from scratch, the shared library could focus on the part that actually differentiated the products:
The UI layer and the system around it.
This allowed the team to avoid spending significant time rebuilding already-solved component mechanics.
Why Nx?
The three products needed to evolve independently while still consuming the same shared frontend foundation.
I chose Nx as the underlying workspace architecture.
The structure was roughly:
apps/ product-a product-b product-c libs/ ui/ icons/ utilities/
The important benefit wasn't simply having everything in one repository.
It was the ability to allow multiple areas of the product to evolve in parallel while maintaining a shared foundation without developers having to coordinate changes across multiple repositories.
This made the developer experience significantly smoother.
Nx also provided a mature CLI and ecosystem around the workspace, and integrating tooling such as Storybook was straightforward.
The most important architectural decision
The easiest mistake when building a shared component library is to make everything reusable.
I deliberately chose not to do that.
The shared library had one fundamental responsibility:
Present UI.
A shared component should be a dumb presentation layer whose behavior is driven by its inputs.
It should not:
- Call APIs
- Contain product business logic
- Know about a specific product workflow
- Make domain-specific decisions
- Become coupled to a particular application
Conceptually:
Product Application
│
Business Logic
│
API/Data
│
↓
Shared UI Library
│
Presentation Only
│
↓
User Interface
This boundary became one of the most important rules of the system.
The component boundary test
We established a simple way to decide whether something belonged in the shared library.
When discussing a component, we would essentially ask:
Can we name this component without using a product-specific concept?
If we couldn't, that was a strong signal that the component belonged to the product rather than the shared design system.
This became our team's somewhat informal test for avoiding business-specific components in the shared layer.
For example:
Shared
DataTable·DatePicker·FormField·Button
Product-specific
LoanApplicationStatus·CustomerCreditLimit·PaymentApprovalPanel
The distinction wasn't about whether something could technically be reused.
It was about whether it should be reused.
Why this separation mattered
This boundary provided an unexpected benefit beyond reuse.
It made troubleshooting significantly easier.
When something went wrong, the architecture gave us a natural way to narrow the investigation.
Is the problem in the shared presentation layer?
│
┌─────┴─────┐
│ │
Yes No
│ │
Shared UI Product
Business Logic
Because the shared components were intentionally isolated from API calls and business logic, a bug could be narrowed down much faster.
If the component itself was rendering incorrectly, the shared UI library was the natural place to investigate.
If the behavior depended on business rules or API data, the investigation stayed within the product layer.
The architecture therefore became a debugging boundary as well as a reuse boundary.
Building the system
The library grew to 30+ reusable components, covering the common UI patterns required by the products.
The implementation wasn't done in isolation.
I worked closely with the designer to translate the visual system into reusable engineering primitives.
My involvement included:
- Establishing the token structure
- Identifying reusable patterns
- Discussing component variants
- Identifying edge cases
- Helping determine what should and shouldn't become a shared component
- Designing the technical architecture
- Implementing the components
- Establishing the Nx structure
- Configuring Storybook
- Defining contribution rules
The designer remained responsible for the UI/UX design, while I helped make sure the designs could become a maintainable component system rather than a collection of one-off screens.
Storybook as the collaboration layer
Storybook became more than a documentation website.
It served as a shared environment where developers and the designer could interact with the components independently from the products.
We used it for:
Documentation
Developers could understand how components were intended to be consumed.
Visual interaction
Different states and variants could be explored without navigating through an entire application.
Testing
Component behavior and visual states could be exercised independently.
Design/development collaboration
The designer and developers had a common place to review the actual implementation rather than relying exclusively on static designs.
This reduced the gap between:
Design → Component → Product
The design-system rule: don't over-share
One of the most important principles was:
A shared library should remain shared.
It sounds obvious, but as products grow, teams naturally start requesting increasingly specific variations.
A product team might ask:
"Can we add this special behavior to the shared component?"
Technically, the answer might be yes.
Architecturally, the answer might be no.
Every addition had to be evaluated against the purpose of the shared library.
If a component started requiring:
- API calls
- Business logic
- Product-specific state
- Domain-specific behavior
then it was no longer simply a UI primitive.
It belonged in the product.
The trade-off: flexibility vs consistency
The architecture initially benefited from strict consistency.
Tokens and shared components provided a controlled visual language across the products.
But as the products evolved, new requirements appeared that didn't always fit perfectly into the original system.
This created a natural tension:
Consistency vs. Product flexibility
Being too strict could make the design system slow product development.
Being too flexible could turn the shared library into a collection of exceptions.
The solution was to progressively introduce controlled customization.
The principle became:
Customize where necessary, but don't compromise the foundations.
Some aspects could evolve to accommodate product needs, while foundational concerns such as:
- Accessibility
- Interaction patterns
- Component mechanics
- Core design principles
remained non-negotiable.
What changed
After introducing the shared UI foundation, the team saw improvements across both the product and development experience.
Consistency
The three products began using the same UI language instead of independently implementing similar patterns.
Development speed
Developers no longer needed to repeatedly implement common UI patterns from scratch.
Maintainability
Common UI behavior and styling had a single place to evolve.
Collaboration
Design and development had a shared component vocabulary and Storybook environment.
Accessibility
By leveraging PrimeNG's underlying component mechanics rather than rebuilding them independently, the team avoided taking on unnecessary accessibility implementation work.
Debugging
The separation between shared presentation and product-specific logic made it easier to identify where a problem belonged.
Developer experience
Nx provided a unified workspace for multiple products and shared libraries, avoiding the friction of coordinating changes across separate repositories.
What I learned
The biggest lesson wasn't about Nx, Storybook, or even component architecture.
It was about boundaries.
A design system doesn't become valuable simply because it contains many reusable components.
It becomes valuable when the team understands:
What belongs inside it — and what deliberately doesn't.
A shared library that contains everything eventually becomes a dependency that nobody wants to change.
A shared library with a clear responsibility can become infrastructure that multiple products can confidently build upon.
What I would do differently today
Looking back, I would involve the designer even more during the early architecture phase.
The more variants and edge cases we can identify before implementation, the better the component architecture can accommodate them cleanly.
Sometimes what initially looks like one component is actually two different components sharing some visual foundations.
I would also introduce customization capabilities earlier.
Product requirements inevitably evolve, so designing controlled extension points from the beginning can prevent the team from having to retrofit flexibility later.
The key, however, would remain the same:
Customization should never compromise the foundations.
Accessibility, core interaction patterns, and the integrity of the design system should remain non-negotiable.
The architecture in one sentence
Build the shared library as a predictable UI foundation, not as a second application.
That principle allowed three banking products to share more than components.
They shared a frontend language, development workflow, and architectural boundary.
Interested in working together?
If you're building a design system, structuring a shared frontend, or scaling a multi-product architecture — let's talk.
Get in touch