Managed IT · October 6, 2026 · intSignal Team

Your CSS Override Is Correct and Still Ignored: Load Order in Hybrid Sites

Share this article

Half-migrated sites produce a particular kind of CSS bug. You write a rule, it is correct, the element does not change. You check the selector in devtools — it matches. You check for a typo — there isn't one. You add !important, it works, and you move on feeling slightly dirty.

On our own site, running a modern framework over a large body of inherited markup, the explanation was boringly mechanical: two stylesheets, and the legacy one loads second.

<link rel="stylesheet" href="/_next/static/css/app-generated.css">   <!-- new -->
<link rel="stylesheet" href="/css/site.css">                         <!-- legacy, wins ties -->

With equal specificity, the later rule wins. Every override written in the new stylesheet against a selector the legacy sheet also defines loses silently. Nothing is wrong with the rule. It is simply being overwritten after the fact.

Diagnose it before you fix it

Devtools tells you exactly this, but only if you read the right panel. In Elements → Styles, a rule that is being beaten shows with a strikethrough and the winning rule appears above it, with its source file. Click through to the source — if it is a stylesheet you did not expect to be involved, you have found it.

Confirm the order from the document rather than from memory:

[...document.styleSheets].map((s) => s.href);

The array is in document order. Anything after your stylesheet can beat it at equal specificity.

And check what actually won:

getComputedStyle(document.querySelector(".thing")).backgroundColor;

This distinguishes three different problems people routinely conflate: the rule did not match, the rule matched and lost, or the property is not inherited the way you assumed.

Fixes, best first

1. Fix the load order

If you control how the stylesheets are included, put the legacy sheet first so newer rules come after it. In a framework that injects its own CSS, that usually means importing the legacy sheet at the top of the root layout rather than linking it separately in the document head.

This is the real fix. It makes every subsequent override behave the way the author expects, instead of leaving a trap for the next person.

2. Scope with a prefix you control

When the order cannot be changed — the legacy sheet is injected by a platform, or it is loaded from somewhere you do not own — raise specificity deliberately and uniformly rather than per-rule. Prefix overrides with a selector that always matches:

/* public/css/site.css loads AFTER the generated stylesheet, so same-specificity
   rules there win. Overrides are prefixed with `body` to beat it predictably. */
body .tier-card { border-color: #e2e7ee; }

A body prefix adds one element's worth of specificity — enough to win a tie, low enough that a more specific rule can still override it later. The comment is not optional. The next person will see a pointless-looking body prefix and tidy it away unless the reason is written down.

3. Use a layer, if your stack allows it

@layer makes precedence explicit and independent of source order:

@layer legacy, app;

@layer legacy { /* inherited rules */ }
@layer app    { /* your rules — win regardless of where they appear */ }

Everything in a later layer beats everything in an earlier one, whatever the specificity. This is the cleanest answer for a long-running migration, with the caveat that unlayered CSS beats all layered CSS — so a stylesheet you cannot modify will still win unless you can wrap it.

4. !important, knowingly

It is not forbidden. It is a statement that this rule must win, and in a hybrid codebase that is sometimes true. But reach for it only after you know why you are losing, and leave the reason:

/* Legacy theme sets this inline on every card; nothing else can beat it. */
.card { background: #fff !important; }

The problem with !important as a first move is that it works, so the real cause is never found — and the next twenty overrides each need their own, until the stylesheet is a specificity arms race.

The trap: it only shows up in production

Our build concatenated and ordered things differently in development than in the exported static site. A rule that behaved locally lost once deployed. If your framework handles CSS differently between dev and production — and most do — check the built output, not just the dev server:

npm run build
grep -o 'href="[^"]*\.css"' out/index.html

That is the order that matters. It takes ten seconds and it is the difference between fixing the bug and fixing the symptom.

Why this is worth the discipline

In a migration the old stylesheet is usually large, old, and written against markup that is being replaced. Every !important added to work around its load order makes the eventual deletion harder, because now you cannot tell which overrides exist for a real design reason and which exist only to win a fight with a file you are about to delete.

Fix the order once and the rest of the migration gets easier. Paper over it and each fix adds to the bill.

Share this article