CSSDebugging CSS

Debugging CSS

Every CSS bug eventually reduces to one of a small number of causes: specificity, a typo, cascade order, or a layout model you did not expect (flexbox default sizing, stacking contexts, box-model math). A systematic process gets you to the actual cause faster than guessing and randomly changing values until something looks right.

Step 1: the outline trick

Before anything else, make every box visible. This single line reveals unexpected margins, overlapping elements, and mystery whitespace instantly.

CSS
* {
  outline: 1px solid red;
}

/* Or, to see nesting depth at a glance: */
* {
  outline: 1px solid hsl(calc(var(--depth, 0) * 40) 80% 50%);
}
Paste it straight into DevTools
You do not need to edit a stylesheet for this — open DevTools, go to the Elements panel, click the "+" to add a new rule (or use the Styles pane's inline editor) on the * selector, or just run it from the console: document.styleSheets[0].insertRule("* { outline: 1px solid red }").
Step 2: the DevTools inspect flow
  1. Right-click the element and choose Inspect (or use the element-picker icon, top-left of the Elements panel)

  2. Check the Styles pane: rules are listed most-specific/most-recent first, with strikethrough on any overridden declaration

  3. Look for a strikethrough on the property you expect to apply — that tells you something else is winning

  4. Click the source link next to a rule to jump straight to the stylesheet and line number that defined it

Step 3: the Computed panel

The Styles pane shows what was written; the Computed panel shows what actually won after the entire cascade resolved. When you are not sure which rule is in effect, Computed is the ground truth.

  • Computed panel shows the final, resolved value for every property on the selected element

  • Click the arrow next to a computed value to see exactly which rule (and which stylesheet/line) provided it

  • Use the filter box at the top of Computed to jump straight to one property (e.g. type "margin")

Step 4: finding overflow culprits

A horizontal scrollbar appearing unexpectedly almost always means one element is wider than its container. Binary-search for it instead of guessing.

CSS
/* Quickly find which element is overflowing horizontally */
* {
  outline: 1px solid red;
}

/* Or, more surgically, in the DevTools console: */
/* [...document.querySelectorAll('*')].forEach(el => {
     if (el.scrollWidth > document.documentElement.clientWidth) {
       console.log(el);
     }
   }) */
Common overflow culprits
Fixed-width elements wider than their container, unbroken long text or URLs, an image without max-width: 100%, and negative margins pulling content past the viewport edge are the most frequent causes of unexpected horizontal scroll.
Step 5: layout shift debugging
  • Open DevTools → Performance panel → record a page load or interaction

  • Look for "Layout Shift" entries in the timeline; clicking one highlights exactly which element moved

  • Lighthouse also reports Cumulative Layout Shift (CLS) with the specific elements responsible

  • Common causes: images/ads without reserved space, web fonts swapping in in without font-display tuning, content injected above existing content

Step 6: bisecting with display: none

When you cannot tell which of several elements is causing a layout problem, hide them one at a time (or half at a time, for a true binary search) until the problem disappears — the last element you hid is the culprit.

CSS
/* Toggle in DevTools: select the element, press "H" (Chrome/Firefox
   shortcut) to instantly toggle display: none without editing CSS */

.suspect-element {
  display: none !important; /* temporary, for debugging only */
}
Keyboard shortcut
With an element selected in the Elements panel, pressing H toggles its visibility instantly (Chrome and Firefox both support this) — much faster than manually adding a display: none rule and removing it afterward.
"Why isn't it applying?" — the usual suspects

Symptom

Likely cause

How to confirm

Rule shows strikethrough in Styles panel

A more specific (or later) rule overrides it

Check the Computed panel to see which rule actually won

Rule does not appear in Styles panel at all

Typo in the selector, or the class/id is not actually on the element

Search the Elements panel for the exact class name to confirm it is present

Property is set but has no visible effect

Wrong property for the display type (e.g. vertical-align on a block element)

Check the element's computed display value first

Rule is defined but never wins

A @layer order issue — later layers beat earlier ones regardless of specificity

Check the Styles panel for a layer name next to the rule

Style works in one place, not another

Cascade layers, media query mismatch, or a container query condition not met

Check the conditions listed above the rule in the Styles panel

Specificity is not the only reason a rule loses
It is tempting to always blame specificity, but a rule can also lose because of source order (later wins ties), cascade layers (layer order beats specificity), or simply because the selector never matched the element in the first place due to a typo. Check the Computed panel before assuming it is a specificity problem.
Debugging cascade layers

Cascade layers (@layer) change the usual rule that "higher specificity always wins" — a layer declared later beats an earlier layer regardless of specificity, which surprises people the first time they hit it.

CSS
@layer reset, base, components, utilities;

@layer base {
  .button { color: blue; }
}

@layer utilities {
  /* Wins even though .button here has LOWER specificity than
     an id selector in an earlier layer, because utilities is
     declared later in the @layer order */
  .button { color: red; }
}
Reading layer order in DevTools
The Styles pane shows the layer name next to each matched rule. If a rule you expect to win is losing, check whether it sits in an earlier-declared layer than the rule that actually applied — cascade layer order settles the fight before specificity is even considered.
Debugging container queries

A @container rule that never seems to trigger is almost always a missing container-type declaration on the intended ancestor — without it, the element is never established as a query container, and the query silently never matches.

CSS
.sidebar {
  container-type: inline-size; /* REQUIRED for @container to work */
  container-name: sidebar;
}

@container sidebar (min-width: 300px) {
  .widget {
    grid-template-columns: 1fr 1fr;
  }
}
  • Confirm container-type is actually set on the intended ancestor — check the Computed panel for that specific element

  • In the Elements panel, container-query-aware DevTools show a small "container" badge on elements that qualify, similar to the flex/grid badges

  • Resize the DevTools viewport (not the browser window) to trigger a container query bound to viewport-relative container size, and watch the badge/overlay update

Debugging custom properties

A custom property that "does nothing" is usually either misspelled, or its value was never actually set on an ancestor the way you assumed. The Computed panel resolves the mystery by showing the actual final value the var() call received.

CSS
:root {
  --brand-color: #2563eb;
}

.button {
  color: var(--brand-colour, black); /* typo: "colour" vs "color" --
                                          silently falls back to black */
}
Custom properties fail silently
An undefined custom property does not throw an error or produce a strikethrough rule in the Styles panel the way a normal typo would — it simply resolves to the fallback value (or the initial value if there is no fallback). Search the Computed panel for the exact variable name to confirm it is actually defined and spelled correctly.
Debugging on a real mobile device

Some bugs (safe-area insets, real touch behavior, actual viewport height quirks) only show up on a physical device. Both Chrome and Safari support remote debugging a connected phone directly from desktop DevTools.

  1. Connect the phone via USB and enable developer/remote debugging in its browser settings

  2. Open chrome://inspect (Chrome) or Safari's Develop menu (Safari) on the desktop

  3. Select the connected device's open tab to get a full desktop DevTools session mirroring the real device

  4. Use this for any bug report that only reproduces "on my phone" — most such bugs come down to safe-area insets, dynamic viewport units, or touch-specific interaction states

Next
Learn to read the exact spacing values behind a layout bug in Inspecting the Box Model in DevTools.