Topic 11.6
Debugging CSS
In one line
Debugging CSS is a routine, not guesswork: inspect the element, check whether the rule matches and wins, read the computed value and the box model, use the Flexbox and Grid overlays, and know the handful of bugs that cause most layout problems.
Think of it like this
A doctor's check-up. A good doctor doesn't guess; she asks questions in a fixed order (temperature, pulse, blood pressure) and each answer narrows down the cause. CSS debugging works the same way. Instead of changing random values until it looks right, you ask the same questions every time: Does my rule match this element? Does it win? What is the final value? What is the box size, and who decides it?
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- DevTools
- The developer tools built into every browser, opened with F12 or right-click and Inspect.
- Styles pane
- The DevTools list of every CSS rule that matches the selected element, strongest first.
- Computed tab
- The DevTools view of each property's final value and which rule set it.
- Struck-through declaration
- A declaration that matched but lost the cascade to a stronger one.
- Overlay
- Lines and labels DevTools draws over the page to show a grid, flex container or box.
- Margin collapsing
- Vertical margins that touch merge into one margin, which can make a child's margin push its parent.
- Stacking context
- A group of elements stacked together as one layer;
z-indexonly compares elements inside the same group. - Reduced test case
- The smallest piece of HTML and CSS that still shows the bug.
- Bisecting
- Finding a problem by removing half the suspects at a time until one is left.
Step by step
01Ask: does my rule match?
Inspect the element and look for your rule in the Styles pane. If it isn't listed at all, the selector doesn't match this element or the file didn't load. Check the class in the Elements panel against the selector, letter by letter.
02Ask: does it win?
If your declaration is struck through, open the Computed tab, expand the property, and you'll see every candidate with the winner first. Compare origin, layer, specificity and order (Phase 2) to see why.
03Ask: what does the box look like?
Read the box model diagram. Hover the element in the Elements panel and look at the coloured areas on the page. Unexpected orange is a margin, unexpected green is padding.
04Turn on the layout overlays
Click the grid or flex badge next to the container. The overlay shows track lines, gaps and item sizes, so you can see whether a column is 1fr or stuck at its content size.
05Hunt overflow with a snippet
Paste this in the Console to list every element whose right edge is past the viewport. Then fix the widest one first.
const vw = document.documentElement.clientWidth;
[...document.querySelectorAll("body *")]
.filter((el) => el.getBoundingClientRect().right > vw)
.forEach((el) => console.log(el));06Reduce, fix, and copy back
Strip the case down until the bug is obvious, fix it in DevTools, then open the Changes tab to copy exactly what you changed into your source files.
Try it yourself
- 1
Find the culprit yourself
In the first example, add
<p>https://example.com/a-very-long-address-with-no-spaces-at-all-that-will-not-wrap-anywhere-on-a-phone-screen</p>and give the bannermax-width: 100%. Predict whether the page still scrolls sideways in a narrow window, then check. (Fix it withoverflow-wrap: anywhere, Phase 4.) - 2
Fix the escaping margin another way
In the second example, instead of
flow-root, give.wrappadding-top: 1px. Predict the gap the console prints, then run it. - 3
Use the overlay
Open DevTools on the third example, find the
flexbadge next to<header>and click it. Predict where the free space appears withjustify-content: space-between, then compare with the overlay.
Code & diagrams
Change `width: 5000px` to `max-width: 100%` and run again: the console finds nothing and the page no longer scrolls sideways.
Preview
Console
Nothing logged yet.Expected console output
page scrolls sideways: true
too wide: div.banner`height: 50%` needs a parent with a definite height, so it resolves to `auto` (0px for an empty box) until the parent gets one. The paragraph's 24px margin collapses through `.wrap` and pushes the whole yellow box down; `display: flow-root` makes `.wrap` contain it.
Preview
Console
Nothing logged yet.Expected console output
child height, parent auto: 0px
child height, parent 200px: 100px
gap above .wrap: 24
gap above .wrap with flow-root: 0Each red line is one element's border box. Notice the small gap under the green image: images are inline and sit on the text baseline. Add `img { display: block; }` and the gap disappears. Delete the outline rule when you're done.
Preview
Console
Nothing logged yet.Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
A flex item that refuses to shrink
Put a long unbroken word inside a flex item next to a fixed-width sidebar: .row { display: flex } .side { width: 200px } .main { flex: 1 }.
Break #2
Using borders to debug
In the third example, replace the outline debug rule with * { border: 1px solid red; }.
Myth vs fact
Myth
If a style doesn't apply, add !important.
Fact
That hides the cause and starts a specificity war. Find out in DevTools whether the rule doesn't match, loses, or has no effect, and fix that.
Myth
What you see in View Source is what the browser is styling.
Fact
View Source shows the HTML as downloaded. The Elements panel shows the live DOM after scripts changed it, which is what CSS actually matches.
Myth
Layout bugs are usually browser bugs.
Fact
Nearly all of them are spec behaviour that surprised someone: margin collapse, min-width: auto, stacking contexts, percentage heights. Reduce the case first; real browser bugs are rare.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
Chrome shows why a declaration has no effect: hover the info icon on a dimmed property to read messages such as "The display: block property prevents justify-content from having an effect". Firefox's inactive CSS feature does the same.
- ▸
getComputedStyle()returns the resolved value, whilegetBoundingClientRect()returns the real painted rectangle including transforms. When they disagree (atransform: scale()for example), the transform is the reason. - ▸
The Rendering tab's Layout Shift Regions and the Performance panel find layout shifts, which hurt the Cumulative Layout Shift metric (Core Web Vitals). Common causes are images without
width/height, late web fonts and content injected above other content. - ▸
Container queries,
:has()and layers all appear in the Styles pane with their conditions (@container,@layernames), so check the conditions there when a rule seems to switch on and off.
Remember this
- 1
Step 1: inspect the right element. Right-click it and choose Inspect, or use the element picker in DevTools. Check you selected the element you think you did; many bugs are styles on a wrapper while you're looking at the child. The Elements panel shows the DOM tree as it is now, after JavaScript changed it, not your original HTML.
- 2
Step 2: read the Styles pane. It lists every rule that matches, strongest first. If your rule is missing, the selector doesn't match (check spelling, classes, nesting, or whether the stylesheet loaded at all). If it's there but struck through, another declaration won; hover the property in the Computed tab to see which rule won and why. A warning icon means the value is invalid (a typo like
colror20 px) and was ignored. A greyed-out declaration with an info icon means it is valid but has no effect here, for examplejustify-contenton an element that isn't a flex or grid container; Chrome explains why when you hover it. - 3
Step 3: check the box. The box model diagram in the Computed tab shows content, padding, border and margin. Hovering an element in the Elements panel paints its margin (orange), padding (green) and content (blue) on the page. Most "why is there a gap" questions are answered here: an unexpected margin, padding from the browser's own styles, or margin collapsing (two vertical margins merging, or a child's margin escaping its parent, Phase 5).
- 4
Step 4: use the layout tools. Elements with
display: flexorgridget a flex or grid badge in the Elements panel; clicking it draws an overlay with lines, gaps and track sizes. The Layout pane lists every grid and flex container on the page. Chrome's flex editor and Firefox's Flexbox inspector even show how much each item grew or shrank and why. For stacking problems, the Layers panel and the Rendering tab's options (paint flashing, layout shift regions) show what the browser repaints and what moves. - 5
A few bugs cause most CSS pain, so learn their signatures. Horizontal scroll: something is wider than the screen (a fixed width, a long URL,
100vwplus a scrollbar). Flex item won't shrink: flex items havemin-width: auto, so long content keeps them wide; addmin-width: 0(Phase 7). Percentage height does nothing: the parent has no definite height, soheight: 50%behaves likeauto. z-index ignored: the element isn't positioned, or its parent created a stacking context (Phase 6). Gap below an image: images are inline and sit on the text baseline; usedisplay: block. Margin escaping the parent: margin collapse; use padding,display: flow-root, or flex/grid on the parent. - 6
Two quick tricks help when you can't see the boxes.
* { outline: 1px solid rgb(255 0 0 / 0.4); }outlines every element without changing layout (outlines take no space, unlike borders). And when you don't know which element breaks the layout, bisect: delete or hide half of the suspects, see if the bug stays, and repeat on the half that has it. A short JavaScript snippet can also list elements wider than the viewport, as the first example shows. - 7
Finally, reduce the case. Copy the broken part into a small file (or a Hectal live example) with the fewest elements and rules that still show the bug. Most bugs become obvious in a 10-line case, and if it's really a browser bug, a reduced case is what you'd attach to a bug report. Use DevTools' Changes tab to collect the edits you made while experimenting, then copy them back into your source files.
Explain it without notes
Walk through how you debug "my CSS isn't applying".
A page scrolls horizontally on mobile. How do you find and fix the cause?
Why might z-index: 9999 not bring an element to the front?
Why does a child's margin-top sometimes move its parent, and how do you stop it?
Practice
A card in a flex row overflows when its title is a long word. Write the CSS fix and explain it.
Write a debug stylesheet rule that shows the boxes of every element inside .layout without changing the layout.
Trade-offs
- ↔
Fixing in DevTools is fast but temporary; always copy the change back to source (the Changes tab helps) and check it in the real file's context.
- ↔
overflow: hiddencan hide a symptom quickly, but it also clips focus rings, shadows and dropdowns, and the real cause is still there.
Done when you can
I can tell whether a rule doesn't match, loses, is invalid, or has no effect.
I can read the Computed tab and the box model diagram.
I can use the Flexbox and Grid overlays.
I can find what causes horizontal scrolling.
I know the signatures of the common bugs:
min-width: auto, percentage heights, z-index, image gaps, margin collapse.I can reduce a bug to a small test case.