Highwire: Global Component System Overhaul

When I joined Highwire, I saw an opportunity to simplify development, unify design, and clean out years of CSS debt across a sprawling Angular app.


Overview

Highwire's frontend was fractured and inconsistent. Styles conflicted. Components were duplicated. CSS overrides piled up across the app. Developers were building their own versions of the same UI patterns instead of sharing them, and teams weren't communicating about what already existed.

This wasn't just a styling problem. It slowed delivery, created a disjointed experience for users, and made every new feature harder to build than it needed to be.

This project was the culmination of my work at Highwire and my main focus as a UX engineer. I pitched it when I joined: we'd never get consistency unless we aligned on a shared component system, cleaned up the CSS, and gave developers a single source of truth.

My goal: Build a unified, scalable component system that aligned design and development while removing legacy styling at the root.

Role

Lead UX Engineer

Team

~30 engineers, 1 designer

Timeline

Ongoing, 6+ months


The Problem

The core issue was a disconnect between the design world and the development world. Design might be working toward a unified system in Figma, but that didn't mean much if development didn't have the same understanding. What looked cohesive in design could end up completely fragmented in code depending on the tech stack, the developer's background, and whether anyone was actually guiding the frontend.

At Highwire, most of our engineers were stronger in backend development with Java. Nobody was really guiding the frontend. That gap showed up everywhere:

  • PrimeNG, Bootstrap, and legacy styles constantly conflicted
  • !important was everywhere
  • Developers made their own versions of common patterns (like a plain div with colored text instead of a pill component)
  • Multiple teams were putting "global" components in different places, and nobody knew what already existed
  • There was no reliable system for translating designs into code

The system needed to do more than just modernize styles. It needed to bridge that gap between design and development across a team of 30 without introducing new tech debt.


1. Starting with Cleanup

I started with subtraction. Using a script, I scanned for unused global CSS classes across the codebase.

CSS usage audit results

The script flagged thousands of dead classes. But many deeply embedded styles, especially from Bootstrap and PrimeNG, had to be removed manually.

One by one, I replaced legacy classes with Tailwind utilities, simplifying the styling model and removing bloat from every layer of the stack. By the end, about 80% of the codebase had been migrated to Tailwind. This phase alone took about two months.

0
Global CSS lines removed
0
CSS files removed
0
Reduction in file size

There wasn't much pushback on spending time deleting things because I juggled it alongside my other work. It was something that quietly got done in the background.


2. Tailwind as the Foundation

Tailwind was already in the codebase when I joined, but it wasn't being used as the foundation. We were leaning more on semantic class names, and styles loaded in an unpredictable order that led to constant override battles.

I restructured our style hierarchy in angular.json so that Tailwind sat at the base, with Bootstrap and PrimeNG layered above it in a way that respected the design tokens and avoided collisions. I also restructured our design in Figma to match Tailwind's utility-first approach so that design and code spoke the same language.

angular.json
"styles": [
          "node_modules/bootstrap/dist/css/bootstrap.min.css", // Bootstrap
          "projects/primeng/css/hw-theme.css", // Primeng Theme
          "projects/frontend/src/styles.css", // Global Styles
          "projects/frontend/src/tailwind-styles.css", // Tailwind Styles
          "node_modules/@fortawesome/fontawesome-pro/css/fontawesome.css", // Font Awesome
          "node_modules/primeng/resources/primeng.min.css", // More Primeng
          ...
  ],

Before: global styles loaded in unpredictable order, leading to constant override battles.

angular.json
"styles": [
      "projects/app-frontend/src/styles.css", // Tailwind + Global css
      "projects/app-frontend/src/bootstrap.scss", // Bootstrap
      "projects/ui-theme/src/primeng.scss", // Primeng
      "node_modules/@fortawesome/fontawesome-pro/css/fontawesome.css", // Font Awesome
      ...
  ],

After: Tailwind-first architecture with isolated, scoped overrides from third-party libraries.

This was a risky change. Reorganizing the style loading order broke a lot of things across the app. I knew it had to be done, but I underestimated the blast radius. I ended up rewriting a significant chunk of the frontend to get everything working again. Being fast at writing frontend code is what made it manageable, but it was a lesson in scoping the impact of foundational changes.

The result was worth it: visual consistency, no more specificity wars, and a clean foundation for building reusable components.


3. Component Design Strategy

Once Tailwind was in place, I turned my focus to the components themselves.

I worked with our designer to identify recurring patterns in Figma (cards, buttons, pills, tables) and documented how they appeared across the product. My rule of thumb was that a pattern had to show up in at least 2-3 places before it became a shared component. I didn't want to proactively split things out and over-engineer. I wanted to make decisions based on friction instead of trying to anticipate problems.

UI pattern audit in Figma

Out of that audit came a standardized set of inputs, variants, and visual rules. PMs saw me as the go-to for components and Figma, and I had buy-in from the team to drive this forward.

A Figma walkthrough of one of the various components I created.

The result was reusable, composable components with inputs mapped directly to Figma. Design and dev spoke the same language.


4. Building the Components

We had recently upgraded from Angular 13 to Angular 19, which gave us access to signals. This was a big deal. Before, we were loading everything on ngOnInit and components weren't reactive. They wouldn't update unless you refreshed the page. With signals, I could create small, efficient components with built-in caching and reactivity right out of the gate.

button.component.ts
export class ButtonComponent {
size = input<'xs' | 'sm' | 'base' | 'lg'>('base');
variant = input<'primary' | 'text' | 'light'>('primary');
color = input<'blue' | 'gray' | 'red'>('blue');
label = input<string>();
roundedFull = input<boolean>(false);

classes = computed(() => {
  const sizeClass = {
    xs: 'px-1.5 py-1 text-xs',
    sm: 'px-2 py-[0.3125rem] text-sm',
    base: 'px-3 py-[0.4375rem] text-base',
    lg: 'px-3 py-[0.5625rem] text-xl',
  }[this.size() || 'base'];

  const colorClass = {
    primary: {
      blue: 'bg-blue hover:bg-blue-700 border-blue text-white',
      gray: 'bg-gray-600 hover:bg-gray-700 border-gray-600 text-white',
      red: 'bg-red hover:bg-red-700 border-red text-white',
    },
    text: {
      blue: 'text-blue hover:text-blue-700',
      gray: 'text-gray-600 hover:text-gray-700',
      red: 'text-red-600 hover:text-red-700',
    },
    light: {
      blue: 'text-blue',
      gray: 'text-gray-500',
      red: 'text-red-600',
    },
  }[this.variant() || 'primary'][this.color() || 'blue'];

  const variantClass = {
    primary: '',
    text: 'bg-transparent hover:bg-gray-50 border-transparent',
    light: 'bg-gray-50 hover:bg-gray-100 border-gray-100',
  }[this.variant() || 'primary'];

  const roundedClass = this.roundedFull() ? 'rounded-full' : 'rounded-md';

  return `${sizeClass} ${colorClass} ${variantClass} ${roundedClass}`;
});
}

We defined 22+ components in Figma, and I built 10+ of those as fully custom components in code. The rest were themed versions of PrimeNG components where customization made more sense than a full rebuild. We felt like if we could build something ourselves in an hour or two and have full control over it, there was no point using a third-party UI kit component. We deprecated PrimeNG components like p-card and p-pill in favor of our own hw-card and hw-pill.


5. Storybook + Documentation

Developers weren't referencing the design docs in Figma, and honestly, a lot of them just weren't familiar with what global components already existed. I introduced Storybook as the single source of truth.

We needed one place to see all of our components, reference what we're building, and stop duplicating effort. Setup took about three weeks, and then it was an ongoing process of documenting every shared component with visual examples and usage rules.

Card component variants

Card component in Storybook with all variants and input combinations documented.

Button variants in Storybook

Button component showing size, color, and variant options.

Being involved in PR reviews also meant I could catch component misuse before it shipped.


6. Developer Enablement

I didn't want to be the sole enforcer of the new component system. To drive adoption, I focused on education and tooling.

I presented at engineering meetings to showcase Storybook and walk through how to use the new components. I wrote docs on best practices and usage patterns.

Confluence Docs

But the biggest lever was ESLint rules. I set up custom rules that flagged deprecated component usage, like warning developers when they imported p-card instead of hw-card, or used the old PrimeNG pill instead of our own. This caught things automatically without me having to review every PR.

Developers are still learning to use Storybook fully, but the ESLint rules have been effective at guiding people toward the right components. As a next step, I'd like to implement AI-powered design guidelines that could be fed into tools like Cursor, so developers get component recommendations as they code.

Since we hadn't taken time to remove old components, I also made it my mission to consolidate them into the new system. We went from 10-15 different versions of things like tables down to one global table that had everything baked in and worked consistently across the app.


Outcome

Here's what the component system looks like in practice across Highwire:

Dashboard with stat cards and graphs

Dashboard using shared stat cards and graph components for a consistent data visualization experience.

Collapsed sidebar with dashboard graphs

Collapsed sidebar layout with reusable dashboard graph components.

Global table with search and views

The global table component with built-in search and preset filter views, replacing 10-15 one-off table implementations.

Table with filters and tabbed card

Tabbed card component toggling between chart views, alongside filtered table data.

Consistent

Pages look and behave the same across the entire app

Faster to build

Shared components mean less custom code per feature

Fewer bugs

Design QA issues dropped significantly

The biggest win was consistency. Before, every team was building their own version of common patterns. Now there's one table, one card, one pill, and they all look right everywhere. Design QA issues dropped because the components enforce the right styling by default.


Takeaways

  • Start with deletion. Cleanup made everything that followed easier to maintain and scale.
  • Feel the pain first. I only built shared components when the same pattern showed up 2-3 times. Making decisions based on friction instead of anticipation kept things practical.
  • Foundational changes have a blast radius. Restructuring the style hierarchy broke things. Being honest about that and being fast enough to fix it is what made it work, but I'd scope it more carefully next time.
  • Figma and code must match. Keeping design and dev aligned reduces bugs, questions, and wasted effort.
  • Thin overrides over heavy theming. Tailwind let us stay flexible without creating another rigid system to maintain.
  • Developer education is infrastructure. The work only matters if other people can use it well. ESLint rules did more for adoption than any meeting.

TL;DR

"I led a full revamp of Highwire's component styling system, removing 5,000+ lines of legacy CSS, migrating 80% of the codebase to Tailwind, aligning design and dev in Figma and Storybook, and enabling faster, more consistent frontend work across our Angular platform."