Skip to content
Kumar Chandrachooda
.NET

CSS by Accretion

A sequel stylesheet four times the size of the original, a sheet per screen, six copied tour themes, and a modal built as one inline HTML string - what a naming convention and a tokens file actually earn.

By Kumar Chandrachooda 15 Jul 2025 7 min read
Strata of stylesheets laid down one rescue at a time

Site.css is 232 lines. Site2.css is 938. The names tell you the order of events and the sizes tell you everything else: at some point the platform's first stylesheet stopped being something you edit and became something you build on top of, and the sequel outgrew the original four times over. Nobody plans a stylesheet called Site2.css. It happens to you.

Part two ended with the observation that the attendance platform's forms lived in JavaScript strings, beyond the reach of the validation framework it bundled. This chapter is the styling half of the same story — how the platform's CSS grew by accretion rather than design, layer on layer, until the only safe move left was to add another layer. The estate's parent series showed this pattern in C# — four copied dashboard controllers, payroll arithmetic in four files. CSS turns out to be the medium in which accretion reaches its purest form, because CSS is the one language in the stack where deleting anything feels dangerous to everyone.

The sequel stylesheet

Here is how a Site2.css comes to exist, reconstructed from the strata. The original Site.css is the project template's file, extended through the early screens until it styles a little of everything. Then a change goes wrong — someone edits a shared rule to fix the dashboard and breaks the login page, because the cascade is global and no one can enumerate a selector's blast radius. The lesson the team learns is not “we need scoping”; the lesson is “never touch Site.css again”. From that day the first sheet is load-bearing archaeology, and new work goes into a fresh file where at least your own rules cannot break history. Repeat for a few years and the fresh file is 938 lines of the same unstructured everything, and people have started to fear it too.

Around the two site sheets grew the per-screen layer: a stylesheet per feature — invented names, real pattern — employee-locator.css, dashboard.css, org-chart.css, report-builder.css, login.css, roughly thirty-five files feeding the seven CSS bundles from part one. A sheet per screen sounds like scoping, and it is — by discipline only. Every rule in every sheet still competes in one global cascade; dashboard.css is perfectly capable of styling the report builder if a selector happens to match. The file boundary documents intent and enforces nothing, which is exactly the arrangement that fails silently when a class name gets reused.

The economics of this arrangement deserve naming, because they are the engine of the whole chapter. In an unstructured global stylesheet, the cheapest safe change is always additive: a new, slightly-more-specific rule that wins over the old one. Editing risks the unknown; adding risks nothing today. So specificity ratchets upward, the sheets only grow, and every addition makes the next edit more dangerous — accretion is not a failure to tidy up, it is the rational strategy inside a system with no safety rails. CSS by accretion is what a team's risk assessment looks like when rendered in stylesheets.

Six copies of a theme

The platform used a guided-tour library to walk new joiners through its screens, and the library ships themeable styles. The estate's approach to theming was to commit six theme stylesheets — dark, modern, royal and three siblings — as six near-duplicate files, differing in a handful of colour and border declarations, identical in the other hundred-odd lines. Someone chose a theme by bundling one of the six; the other five shipped anyway. The folder they lived in carried a typo in its name — Theam for Theme — which is its own small lesson: in a copy-based system, even the misspellings are load-bearing, because six files and a bundle path reference the typo and correcting it is now a seven-file change nobody will risk for spelling.

Theming-by-copy is the accretion pattern applied across files instead of within one. The fix, then as now, is to separate structure from decision — one structural sheet, and tokens for the parts that vary:

:root {
    --tour-bg: #ffffff;
    --tour-ink: #1f2430;
    --tour-accent: #5a5fd6;
    --tour-radius: 6px;
}

[data-tour-theme="dark"] {
    --tour-bg: #1f2430;
    --tour-ink: #f4f5f7;
}

.tour-tooltip {
    background: var(--tour-bg);
    color: var(--tour-ink);
    border-radius: var(--tour-radius);
}

Six files become one file plus a few token blocks; a theme diff becomes readable at a glance; and a fix to tooltip layout lands in every theme automatically instead of in whichever copies remember to receive it. Custom properties were well supported by every browser this intranet served. The copies were not a technology gap — they were the house style, expressed in CSS.

The modal that lives in a string

The layout — a 489-line file doing a great many jobs — carried the pattern at its most concentrated. One status label in the markup wore an inline style attribute twelve properties long: position, colour, font, spacing, all inline, unreachable by any stylesheet without !important. And the office-visit dialog, one of the platform's most-used forms, was built as a single concatenated HTML string handed to the modal library, every element styled inline as it went past:

Swal.fire({
    title: 'Plan an office visit',
    html: '<div style="text-align:left;margin:0 12px">' +
          '<label style="font-weight:600;display:block;margin-bottom:4px">Date</label>' +
          '<input id="visitDate" type="text" style="width:100%;padding:6px 8px;' +
          'border:1px solid #ccd0d6;border-radius:4px" />' +
          '<label style="font-weight:600;display:block;margin:12px 0 4px">Location</label>' +
          '<select id="visitCity" style="width:100%;padding:6px 8px;' +
          'border:1px solid #ccd0d6;border-radius:4px">' +
          '<option>Metro one</option><option>Metro two</option><option>Metro three</option>' +
          '</select></div>',
    showCancelButton: true
});

This is the form that part two's hand-rolled validation guarded, and now you can see why no framework could reach it: it is not markup the server rendered, not markup any stylesheet governs, not markup that exists at all until the string executes. Restyle the platform's inputs and this input does not hear about it. Grep the CSS for the modal's classes and there are none to find. The twelve-property label and the string-built form are the endgame of accretion — inline styles are not a styling decision; they are a decision to stop having styles, made one attribute at a time, and every inline declaration outranks the entire cascade you are still paying to ship.

Class soup without a convention

The class names that did exist answered to no convention. Invented but representative: .stickyHeaderLeftPane, .navItemManagerDash, .cardOuterWrap, .tblFilterRow — camelCase here, abbreviation there, each name minted for one element on one screen, none reused, no utility layer, no block-element-modifier structure, nothing to tell a reader whether a class is a component, a state, or a one-off shim. Semantic-sounding soup: every name means something, and the vocabulary never repeats.

The under-appreciated payoff of a naming convention — BEM or any other, it matters far less which — is not aesthetics. It is deletion confidence. When .report-card__header--collapsed turns up in a stylesheet, its name declares its owner, and if the report card is gone the rule can go. When .tblFilterRow turns up, its owner is a mystery, grep is your only tool, string-built markup defeats even that (as the modal above just demonstrated), and so the rule stays forever. A convention is what makes CSS deletable, and deletable CSS is the only kind that does not accrete.

What would the right-sized alternative have been, for this team, in that era? Not a framework migration. Three files' worth of decisions: a tokens sheet — the colours, spacing steps, radii and font sizes the platform actually uses, as custom properties; a components sheet — the button, the card, the table header, the modal shell, named under one convention; and per-screen files demoted to genuine layout-only leftovers. Every piece of that was achievable in the platform's stack with no build-tool changes, and the modals come back into the fold the moment their markup moves out of strings and into partials the server renders. The design system that this platform needed was not a product to adopt — it was about three days of naming things and one rule: no new inline styles.

The ledger

  • Site.cssSite2.css — the sequel stylesheet is a fear response; the fix is scoping and convention, not a third file.
  • A sheet per screen — file boundaries document intent but enforce nothing; the cascade stays global.
  • Six tour themes by copy — structure once, tokens per theme; the typo'd folder shows how copies fossilise even spelling.
  • The twelve-property inline label and the string-built modal — inline styles defeat theming, grep and the cascade all at once.
  • Soup naming — a convention's real product is deletion confidence; unnamed ownership makes every rule permanent.

One thread is still hanging: the modal string came from JavaScript, and the JavaScript came from — where, exactly? The platform had no module system, a dead entry script, and a page file two thousand nine hundred lines long, and that is the closing chapter: Where JavaScript Goes Without a Module System.