CSSTidy CSS Formatter

Debugging CSS in DevTools: Specificity Conflicts, Struck-Through Rules, and Inherited Mystery

CSSTidy — CSS Formatter & Minifier Guides · Updated 2026-10-12 · All guides

Most CSS debugging is not knowledge of the spec; it is knowing where the browser tells you the answer. Every item below is readable in the panel without changing a line of code.

Read the cascade in the order the browser applies it

In DevTools, Styles shows matched rules bottom-up in source order but the browser applies them top-down in cascade order — the confusing part is that DevTools sorts by specificity in some views and by source order in others. Set the filter to the source-order view (⋮ → "Show all" plus source order) so the rule nearest the top of the list is the one that lost, not the one that won.

Above the rules list are the filter checkboxes: User-agent, User, :hover/:focus, Inherited, :root. Ticking "Inherited" is how you find out that line-height came from html and not from your component. Untick :hover when a rule looks applied but is not — a struck-through :hover rule is struck because the element is not hovered while you inspect it, not because the selector is wrong.

Why the value you wrote is not the value you got

SymptomCheckUsual cause
Rule struck throughthe rule above it that also sets the propertyhigher specificity, or later in source at equal specificity
Rule struck through, no competitor visibletick User-agenta UA rule (input { } styles) is more specific than you think
Property greyed, value presentComputed panel, expand the propertyinvalid value for that property (height: 100% in an indefinite parent) — the declaration is dropped, not overridden
Value present but wrong unitsComputed vs. used valuedvh/vh, fr → px, rem → px after a root font-size change
Nothing sets itthe Inherited rowit is inherited; color and line-height come from an ancestor
Works until you resizehover the element and watch the layout highlightcontainer/media query, not the selector

The single most under-used row is Computed → "Rendered". It shows used values after layout, which is the only way to see that height: 100% computed to 0 because the parent had no definite height, and that flex-basis: auto resolved to content width.

The three-line diagnosis for "it is overridden and I cannot find where"

  1. Select the element, Computed panel, type the property into the filter box.
  2. The panel prints the winning rule and its selector — click it to jump to source, including inside a bundled stylesheet.
  3. If the winner is a UA or browser-extension rule, tick User-agent/User in the Styles filter; extension stylesheets are the most common "phantom override" on a machine that is not yours.

Only after step 3 does !important enter the conversation. The correct fix for losing to a third-party library is a @layer at the right priority or a lower-specificity selector via :where(), not a heavier hammer — see resets and layers and the specificity table.

Layout problems are visible, not computable

  • Hover a grid/flex container in Elements: the overlay draws the tracks and gaps. An "item too wide" bug is usually one track wider than you think, which the overlay shows and the Computed panel does not.
  • Click a flex item and read min-width: auto is the reason a long string cannot shrink and the row overflows. See grid vs flexbox.
  • The layers icon (Layout pane) shows scroll containers, sticky elements, and stacking contexts. A dropdown buried under a "lower" element is a stacking-context problem and the icon shows the context boundary directly — no reading of z-index values required.
  • Paint/flame overdraw in the Performance panel: purple/green overdraw on a page that should be flat means overlapping opaque backgrounds, which is a real cost on a low-end device.

Reproduce it without the app

Right-click the element → Copy → Copy outerHTML, then paste into a blank tab with the stylesheet linked, or into the beautifier to make the extracted markup readable. Two reasons this matters: the bug you cannot reproduce in the app is usually a rule that only applies at one viewport width or after one JS state change, and a minimal reproduction answers "is it the framework or the CSS" in one step.

Two filters worth learning

In the Styles search (Ctrl+F inside DevTools, not the browser's): search a property name to find every rule setting it across all stylesheets — the fastest route to "who is setting overflow on everything". Search a value (z-index: 100) to find a magic number's origin when a component library has generated the selector for you.

What DevTools will not tell you

Which selector you intended. A rule that is absent from the matched list is absent because the selector does not match — a shadow boundary (::part() needed), an iframe, a <dialog> moved to the top layer, or a class name built at runtime with a typo. Check element.classList in the console before checking the cascade again.

Related: patterns and gotchas for the containment and margin-collapse behaviours these panels make visible, and minify safety for the case where the CSS you are debugging has already been rewritten by a build step.

Developer Sponsor / Partner
Copied to clipboard!