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:
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); 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.