← Case studies

Case Study

CSS Architecture Overhaul During a Platform Migration

Before a large AngularJS platform could be rewritten in Angular, years of unstructured CSS needed to be untangled. I extracted inline styles, established a separation-of-concerns file structure, eliminated widespread duplication, and built a client-theming architecture that carried forward into the new platform.

The Problem

The platform was an enterprise energy analytics product with a large, long-running AngularJS codebase. Styling had accumulated the way it tends to in a product worked on by many developers over several years: inline style="" attributes scattered through templates, hardcoded hex values repeated across dozens of files, no shared abstractions, and no file structure that would let you find where a given style lived or whether it had already been defined somewhere else.

The core problem was visibility. Because so much styling was inline, it was impossible to audit for duplication — you couldn't grep for what you couldn't see. Every developer had solved styling problems in their own way, in their own area of the codebase, without any mechanism to discover and reuse what had come before.

Step 1 — Eliminating Inline Styles

The prerequisite for everything else was getting styles out of the templates and into stylesheets. This was not just a cleanup task — it was the prerequisite for being able to see the full picture. Until all styling lived in SCSS, there was no reliable way to identify what was duplicated, what was inconsistent, and what could be unified.

This pass touched dozens of files and involved tens of thousands of lines across the application. It produced no visible change for users, but it made the next steps possible.

Step 2 — Separation of Concerns File Structure

With styles consolidated into files, I reorganised them by concern rather than by feature or component. The new structure grouped styles into discrete areas:

BEFORE dashboard.css trend-chart.css scatter-plot.css homepage.css + inline style="" throughout — no shared definitions AFTER _colors.scss _text.scss _forms.scss _grids.scss & _tables.scss _layout.scss _theme-[client].scss main.scss (imports, order)

Reorganising by concern rather than by feature immediately surfaced the duplication. When every text definition lives in _text.scss, three near-identical heading styles become obvious in a way they never would across three separate feature files.

Step 3 — Resolving Duplication

With the full picture visible, I worked through the duplication systematically. Where styles were genuinely identical, I merged them into a single definition and had everything reference it. Where they were circumstantially similar — the same values used for different reasons in different contexts — I made the relationship explicit using SCSS mixins and @extend placeholders rather than copying values. This meant a future change to a shared value would propagate correctly, and a context-specific override would be clearly visible rather than buried in an unrelated file.

<!-- Before: inline styles with hardcoded values directly in AngularJS templates -->
<div class="dashboard-title" style="color: #2d7bb5; font-size: 18px; font-weight: 600;">...</div>
<div class="modal-header"><h2 style="color: #2d7bb5; font-size: 18px; font-weight: 600;">...</h2></div>
<div class="panel-heading" style="color: #2d7bb5; font-size: 18px; font-weight:600;">...</div>

// After: single definition, inherited where needed
// _text.scss
%heading-primary {
  color: var(--color-heading);
  font-size: $font-size-lg;
  font-weight: $font-weight-semibold;
}

.dashboard-title { @extend %heading-primary; }
.modal-header h2 { @extend %heading-primary; }
.panel-heading    { @extend %heading-primary; }

// _theme-acme.scss  (overwrites on root class set at bootstrap)
.theme-acme {
  --color-heading: var(--color-brand-acme-primary);
}

Step 4 — Client Theming Architecture

The platform served multiple enterprise clients, each with their own visual identity. Previously this was handled inconsistently — patches of hardcoded client-specific values mixed in with general styles, with no clear separation between what was a product default and what was a client override.

I built a theming system where client identity was resolved once at application bootstrap: the client name was mapped to a theme class name and applied to the root element. All client-specific overrides lived in isolated theme partials that targeted descendant selectors under that root class. The application's default styles were clean and unaware of any specific client; themes layered on top.

// app-bootstrap.ts — resolve client theme class at startup
const clientTheme = CLIENT_THEME_MAP[clientName] ?? 'theme-default';
document.documentElement.classList.add(clientTheme);
App bootstrap resolve client name Theme map lookup client → class name Root class applied .theme-acme on <html> Theme partial overrides cascade Default styles have no client awareness — theming is a pure overlay

This architecture carried forward as the basis for theming in the new Angular application. The extraction work done in AngularJS meant the rewrite team had a clean, documented model to implement against rather than inheriting the original mess.

Outcome

The work spanned dozens of files and restructured tens of thousands of lines of styling. The immediate result was a codebase where styling was findable, understandable, and changeable without unintended side effects. The longer-term result was that the rewrite team inherited a clear theming model rather than a tangle of client-specific patches — one less unknown in an already complex migration.