D
P
0

HTML & CSS

A Page-Scoped CSS Override Silently Beat My 'Global' Component on Specificity

June 18, 2026·4 min read
A Page-Scoped CSS Override Silently Beat My 'Global' Component on Specificity

I was cleaning up checkbox styling on a site I built: moving the styled-checkbox rules out of a page-specific CSS file and into a global component stylesheet, so they'd render consistently everywhere. Good idea — except on the /checkout page, where my custom checkbox state (an orange-filled box with a white check) still never appeared. What showed there was the native browser checkbox, the plain grey square.

The strange part: on every other page the styling was perfect. Only /checkout failed — exactly the page I least wanted to break.

The global component looked like this:

.checkbox input[type="checkbox"] {
    position: absolute;
    opacity: 0; /* hide the native input */
}
.checkbox .box {
    width: 18px; height: 18px;
    border: 1px solid #ccc;
}
.checkbox input:checked + .box {
    background: #f97316; /* orange */
    /* white check rendered here */
}

That opacity: 0 is meant to hide the native input so the custom .box can stand in for it. On most pages it worked. On /checkout, the native input stayed visible — meaning my opacity: 0 was losing.

Root cause: an older, more specific selector still wins

I opened DevTools, clicked the native input, and looked at the Computed → Styles panel. It was clear: my opacity: 0 rule was struck through, and one older rule was winning over it:

.checkout-page .checkbox input[type="checkbox"] {
    opacity: 1;       /* ← the winner */
    position: static;
}

This rule was a leftover from the checkbox styles' old home — it used to live in the checkout page-specific file, deliberately forcing the native input to stay visible. I moved the new styles into the global stylesheet but forgot to delete this page-scoped override.

And here's the crux — specificity. Score both selectors:

.checkbox input[type="checkbox"]                 → (0,2,1) = 1 class + 1 attr + 1 element
.checkout-page .checkbox input[type="checkbox"]  → (0,3,1) = 2 classes + 1 attr + 1 element

The old selector carries the extra .checkout-page ancestor, so its specificity is higher. In the cascade, highest specificity wins — regardless of which stylesheet loaded later. So the old opacity: 1 beat my global opacity: 0, the native input was never hidden, and the custom state had no room to render.

The deceptive part: my global rule was loaded later in the document. The common instinct is "last one wins," but source order only breaks ties when specificity is equal. Here it wasn't equal — so .checkout-page won despite being defined first.

How to trace it in DevTools

This is the pattern, so you can recognize it in seconds instead of hours:

  1. Inspect the wrong element (here, the native input that's showing).
  2. Open the Computed tab, find the misbehaving property (opacity), click the arrow to expand it.
  3. DevTools lists all contributing rules from winner to loser. The winner is at the top; the losers are struck through.
  4. Look at the top non-struck rule — that's what's winning. Here: .checkout-page .checkbox input[type="checkbox"].
  5. A selector with an extra ancestor (.checkout-page) is almost always the suspect in a "why only on this page" bug.

The moment you see your global rule struck through while a longer ancestor-prefixed rule wins, the diagnosis is done: this is a specificity war, not a loading-order problem.

The fix: delete the old page-scoped override

No !important, no bumping the global rule's specificity (which just spreads the problem). The correct fix is to delete the page-scoped selector that used to compete with the rule in its old location:

/* DELETE from the checkout file — relic from the old home */
.checkout-page .checkbox input[type="checkbox"] {
    opacity: 1;
    position: static;
}

Once that override is gone, the global .checkbox rules finally win on /checkout like they do everywhere else. The native input hides, and the orange box with the white check finally renders.

Lesson

When you promote a component's CSS from a page-specific file to a "global" stylesheet, don't just move the new rules — also hunt down any page-scoped selectors that previously competed with them. Those selectors often carry extra ancestors (.checkout-page, .single-product, and so on) that give them higher specificity, so they keep winning on exactly the page you care about. Loading order won't save you — specificity decides. Open the Computed panel, see which rule wins and which are struck through, and delete the now-useless override rather than piling !important on top of it.