Debugging HTML and CSS with Developer Tools
Finding out why a page looks wrong: inspecting elements, reading computed styles and the cascade, the box model and layout overlays, device emulation, and validating HTML and CSS.
Opening the developer tools
Every desktop browser has developer tools (often called DevTools) built in. They let you look inside the page you are viewing: its HTML, its CSS, its network requests and its JavaScript. This page is about the HTML and CSS side; for the console, breakpoints and the network panel, see debugging JavaScript.
| To | Windows and Linux | macOS |
|---|---|---|
| Open or close the developer tools | F12 or Ctrl+Shift+I | Cmd+Option+I |
| Pick an element on the page | Ctrl+Shift+C | Cmd+Shift+C |
| Open with one element selected | Right-click the element, then Inspect | |
Chrome, Edge and other Chromium-based browsers share the same tools (the HTML panel is called Elements). Firefox's tools look different but offer the same ideas (its HTML panel is the Inspector). Safari hides its Web Inspector until you enable it in Safari's settings, under Advanced ("Show features for web developers" in recent versions). The names below follow Chrome, with Firefox's names where they differ.
Everything you change in the developer tools is temporary: it affects only your own browser tab and disappears when you reload. That makes them a safe place to experiment, even on a live site. It also means a fix you found there still has to go into the real files.
The element inspector
The element inspector (the Elements panel in Chrome, the Inspector in Firefox) shows the page's HTML as a tree you can expand and collapse. Hover over a line and the matching element lights up on the page; click it to select it and see its styles. You can also edit: double-click a tag, an attribute or a piece of text to change it, drag elements to another place, or press Delete to remove one and see what the page looks like without it.
The DOM, not your source file
The inspector does not show the file you wrote. It shows the
Document Object Model
(DOM): the tree of elements the browser built from your HTML, after
any changes by JavaScript. Usually the two look alike. When they do
not, that difference is a clue. The HTML standard defines exactly
how browsers repair broken markup, and they do it silently. In this
example, a <div> was placed inside a paragraph,
which HTML does not allow:
<p class="meter">
Meter KE-1042
<div class="badge">offline</div>
</p>
p {
border: 2px dashed #6d28d9;
padding: 0.5rem;
}
.badge {
display: inline-block;
padding: 0.1rem 0.5rem;
background: #fee2e2;
color: #991b1b;
}
Instead of one dashed box with the badge inside, the result shows a
dashed box with only the meter number, the badge below it, and a
second, empty dashed box. The parser closed the paragraph as soon as
it met the <div> (a paragraph cannot contain
one), and then turned the stray </p> into a new,
empty paragraph (without the class, which is why the CSS uses the
selector p: so you can see it). Open the developer
tools on this page and inspect the result frame: the DOM reads
<p>…</p><div>…</div><p></p>. The fix is in the HTML, not the CSS: use a
<span> for the badge (see
text and semantic elements
for which elements may go where).
Forcing hover and focus
A style for :hover or :focus-visible is
hard to inspect: the moment you move the mouse to the developer
tools, the hover is gone. The :hov button in the
Styles pane (a :hov toggle in Firefox's Rules pane too)
lets you switch on :hover, :focus,
:focus-visible, :active and other states
for the selected element, so they stay on while you work.
Reading the Styles pane: the cascade made visible
Next to the element tree, the Styles pane (Firefox: Rules) lists every CSS rule whose selector matches the selected element. It is the cascade laid out for you: the rule that wins is at the top, followed by rules with lower priority, then the browser's own defaults (marked "user agent stylesheet"), and finally sections "Inherited from" the parent elements. Each rule shows its file and line number; click it to jump to the source.
Here is a typical help request for Kestrel Energy's portal team: "The Send button on the meter reading form is gray instead of blue, and its text sticks to the edges." Kwame Asante opens the developer tools on the button. Try the same on the result below:
<form id="reading-form">
<label for="kwh">Reading (kWh)</label>
<input id="kwh" type="number">
<button type="submit" class="btn-primary">Send</button>
</form>
#reading-form button {
background: #6b7280;
color: white;
border: 0;
}
.btn-primary {
background: #1d4ed8;
padding: 0.5rem 1rem 0.5rem 1 rem;
justify-content: center;
}
The Styles pane for the button shows three different problems:
-
Crossed out:
background: #1d4ed8in.btn-primaryhas a line through it. Another rule won:#reading-form buttoncontains an ID, so its specificity is higher, and it is listed above.btn-primary. Fix: lower the specificity of the first rule (for example toform button, two type selectors, which loses to one class), not an!important. -
Crossed out with a warning icon: the
paddingline.1 remwith a space is not a valid length, so the browser throws away the whole declaration, including the three values that were fine. That is why the text sits tight against the button's edges. Hover the icon and the tools say "Invalid property value". The same icon appears for a misspelled property name such asbackgroud. -
Dimmed, with an info icon:
justify-contentis valid, but has no effect, because the button is not a flex or grid container. Chrome and Firefox both mark such inactive declarations and explain why when you hover over them.
You can test fixes on the spot: click a value to edit it (the ↑ and ↓ keys change numbers step by step), untick the checkbox next to a declaration to switch it off, or click inside a rule to add a new declaration. The Changes panel (in both Chrome and Firefox) lists everything you edited, so you can copy the working version back into your stylesheet.
Computed styles
The Styles pane shows the competition; the
Computed tab shows the result. A
computed style is the final
value of every property for the selected element, after the cascade,
inheritance and conversions: 2em becomes a number of
pixels, bold becomes 700, a color in any
notation becomes one form. Strictly, the developer tools show the
resolved values, which for sizes such as
width are the actual pixels used in the layout.
The Computed tab is the place to go when you ask:
-
"What is the font size really?" Find
font-size(type in the filter box) and read the pixel value. -
"Where does this value come from?" Expand the
property: the tools list the rules that set it, with the winning
one first and a link to its file and line. For an inherited
property such as
color, that may be a rule on a parent element far up the tree. - "Is this the browser's default?" Tick "Show all" to include properties you never set; their values come from the defaults and from inheritance.
At the top of the Computed tab (in Firefox: the Layout tab), a diagram shows the element's box model with its real numbers:
Reading it answers most spacing questions at once. "Why is there a gap above this heading?" Look at its margin, then at the margin of the element before it; if both have one, they may have collapsed into a single gap (see the box model). You can also double-click a number in the diagram to try another value.
Layout overlays
Flexbox and grid layouts are hard to debug by looking at numbers, because the lines and tracks they create are invisible. A layout overlay draws them on top of the page. In Chrome's Elements panel, a flex container has a small flex badge and a grid container a grid badge next to its tag; click the badge to show the overlay. Firefox has the same badges and a Layout tab with Flexbox and Grid sections. Inspect the result below and click the badges:
<nav class="tabs" aria-label="Account">
<a href="#">Usage</a>
<a href="#">Bills</a>
<a href="#">Meter</a>
</nav>
<div class="cards">
<section>Electricity</section>
<section>Gas</section>
<section>Solar</section>
<section>Contract</section>
</div>
.tabs {
display: flex;
gap: 1.5rem;
margin-bottom: 1rem;
}
.cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
gap: 0.75rem;
}
.cards section {
padding: 1rem;
border: 1px solid #9ca3af;
}
With the grid overlay on, you see the column tracks, the gaps
between them and (if you switch them on in the Layout settings) the
line numbers and area names, which are exactly the numbers you need
for
grid-column and grid-row (see
CSS grid). The flex overlay marks the
free space and the gaps between items, which shows at a glance why
items are or are not spreading out (see
flexbox). Make the browser window
narrower and watch the overlay follow the cards as they wrap.
Finding what causes horizontal scrolling
A page that scrolls sideways on a phone is one of the most common layout bugs. Something is wider than the screen, but what? In this result the frame gets a horizontal scrollbar:
<p class="banner">Scheduled maintenance on Sunday</p>
<p>Details: https://www.example.com/service/maintenance/2026/october/customer-portal-upgrade-window</p>
.banner {
width: 640px;
padding: 0.5rem;
background: #fef3c7;
}
Two elements are to blame here, for different reasons: the banner has a fixed width, and the long address contains no place where the line may break. Ways to find the culprit on a real page:
- Hover over elements in the inspector and watch which highlight sticks out past the right edge of the page.
- Firefox marks the element that causes overflow with an overflow badge in the Inspector.
-
Temporarily add
* { outline: 1px solid red; }in the Styles pane. Outlines do not take up space, so they show every box without changing the layout.
Typical fixes are max-width: 100% instead of a fixed
width, and overflow-wrap: anywhere for
long words and addresses in narrow columns.
Device emulation
Device emulation makes your desktop browser behave like a smaller screen. Click the device icon in the developer tools, or press Ctrl+Shift+M (Cmd+Shift+M on macOS in Chrome, Cmd+Option+M in Firefox) to open Chrome's device mode or Firefox's Responsive Design Mode. You can then:
- choose a preset phone or tablet, or drag the edges to any width, to see your responsive breakpoints switch;
- set the device pixel ratio, to check that images stay sharp on screens with a high pixel density;
- simulate touch input instead of a mouse;
- slow down the network and the processor (throttling), to feel what a cheap phone on a weak connection experiences.
The Rendering panel in Chrome and Edge (open it
from the command menu,
Ctrl+Shift+P, by typing
"Rendering") emulates user preferences as well:
prefers-color-scheme for
dark mode,
prefers-reduced-motion (see
transitions and animations), forced-colors, the print style sheet, and several
kinds of color vision deficiency and blurred vision. That is a quick
way to test a page the way readers with other settings see it.
Validating HTML and CSS
Browsers forgive almost everything: the HTML standard defines how to parse documents "whether they are syntactically correct or not", so a broken page rarely shows an error. It just behaves strangely, as the paragraph example above showed. An HTML validator reads your HTML against the rules of the standard and reports every error the browser would otherwise repair silently. The standard tool is the Nu Html Checker at validator.w3.org/nu: give it a web address, upload a file, or paste the markup.
This fragment looks fine in a browser:
<img src="outage-map.png">
<ul>
<p>Current outages:</p>
<li id="area">Utrecht-Oost</li>
<li id="area">Amersfoort-Noord</li>
</ul>
<a href="/report"><button type="button">Report an outage</button></a>
The validator disagrees:
Error: An img element must have an alt attribute, except under certain conditions. Error: Element p not allowed as child of element ul in this context. Error: Duplicate ID area. Error: The element button must not appear as a descendant of the a element.
Each of these matters even though nothing looks broken: a screen
reader cannot describe the map, the list structure is wrong, a link
to #area or a <label for="area">
finds only the first element, and a button inside a link is two
controls in one that keyboard and screen reader users cannot use
reliably. Fix errors from the top; one early mistake often causes
several reports further down.
The validator also exists as a program you can run on your own
machine or in a build
pipeline, so every change is
checked automatically. It is a Java program (vnu.jar)
that takes file names:
java -jar vnu.jar --errors-only public/index.html
For CSS there is the W3C CSS Validation Service at jigsaw.w3.org/css-validator. It catches typos in properties and values, the same things the Styles pane marks with a warning icon. Treat its reports on very new features with some care: a validator can lag behind what browsers already support, so check such a report against current documentation before "fixing" valid code.
Lighthouse and accessibility checks
Lighthouse is an open-source tool from Google that loads a page, measures it and produces a report with scores from 0 to 100 and a list of concrete problems. It is built into Chrome and Edge as the Lighthouse panel of the developer tools, and runs online as PageSpeed Insights. Its categories are:
- Performance: how fast the page loads and becomes usable, including the Core Web Vitals that can be measured in a test run, with advice such as "Improve image delivery".
-
Accessibility: automated checks such as missing
alternative text, form fields without labels, low color contrast,
missing
langattribute and buttons without a name. - Best Practices: for example HTTPS, errors in the console, images with the wrong aspect ratio.
- SEO: basics such as a title, a meta description and crawlable links (see metadata, sharing and search basics).
Performance 78 Accessibility 84 Best Practices 96 SEO 91 Accessibility ▲ Background and foreground colors do not have a sufficient contrast ratio. ▲ Form elements do not have associated labels. ▲ Image elements do not have [alt] attributes.
Two warnings about the numbers. First, a Lighthouse run is a
lab test on your own machine and connection: scores vary
from run to run, and real visitors on real phones may have a very
different experience. Run it a few times, in a private window
without browser extensions, and treat the score as a trend, not a
grade. Second, an accessibility score of 100 does not mean the page
is accessible. Automated tools can only check what a program can
decide, such as whether an image has an
alt attribute, not whether the text makes sense.
So add a few manual checks, using the developer tools:
- Accessibility pane (in Elements; Firefox has a separate Accessibility panel): shows the selected element's role and accessible name as a screen reader will announce them, and the accessibility tree.
- Contrast in the color picker: click a color swatch in the Styles pane, and Chrome shows the contrast ratio of that text color against its background, with a check mark for the WCAG AA and AAA levels.
- The keyboard: put the mouse away and Tab through the whole page. Can you reach and operate everything, and can you always see where the focus is?
A routine for "it looks wrong"
Most CSS problems fall into a handful of causes. Working through them in order, with the developer tools open, is faster than changing values at random. It is the general troubleshooting method applied to a web page:
-
Is the stylesheet loaded at all? Look for your
file in the Network panel (reload with it open). A
404means a wrong path, often a relative path that works on your machine but not on the server. - Does the selector match? Select the element: if your rule is not in the Styles pane at all, the selector does not match (a typo, a missing class, a different structure than you thought).
- Is the declaration valid? A warning icon means a typo in the property or value, and the whole declaration is ignored.
- Does it lose the cascade? Crossed out means another rule wins; the winner is listed above it.
-
Does the property apply here? A dimmed
declaration with an info icon has no effect in this context, such
as
widthon an inline element orz-indexon an element that is not positioned (see positioning and stacking). - Is it the HTML? Compare the DOM with your source, and run the validator.
- Is it an old file? If your change does not show up at all, the browser may still use a cached copy. A hard reload (Ctrl+Shift+R, or Cmd+Shift+R), or "Disable cache" in the Network panel while the tools are open, rules that out (see cache busting).
| Symptom | Usual cause | Where to look |
|---|---|---|
| Unexpected gap above or below an element | Margin, often a browser default or collapsed margins | Box model diagram (Computed) |
| Element wider than you set |
Padding and border added to width (box-sizing: content-box)
|
Box model diagram, box-sizing in Computed
|
z-index has no effect |
Element not positioned, or a different stacking context | Styles (dimmed declaration), parent elements |
| Flex items do not shrink or wrap |
flex-wrap not set, or a minimum content width
|
Flex overlay, Computed min-width |
| Works locally, broken online | Path or file-name case wrong, file not uploaded, old cached copy | Network panel (404s), hard reload |