CSS Module 1 – How CSS works
Inspecting CSS with browser DevTools
Open DevTools in Chrome, Firefox and Safari, see which CSS rule wins and why, read computed values and the box model, and edit styles live.
What you will learn
- Find which rules match an element, which declaration wins and which are overridden
- Read the computed panel and box-model diagram to explain an element's final size
- Edit and toggle declarations live, then copy the fix back into the stylesheet
- Test dark mode, reduced motion and print styles with the browser's emulation tools
Before you start
What you’ll learn · Before you start
What you will learn
- Find which rules match an element, which declaration wins and which are overridden
- Read the computed panel and box-model diagram to explain an element's final size
- Edit and toggle declarations live, then copy the fix back into the stylesheet
- Test dark mode, reduced motion and print styles with the browser's emulation tools
Before you start
On this page
Every desktop browser has developer tools, DevTools for short, that show exactly how CSS applies to any element on a
page. Right-click an element and choose Inspect or Inspect Element, depending on the browser (Safari shows the
item once its developer features are switched on), and the Elements panel opens with that element selected. Its Styles pane lists every rule that
matches the element, the strongest first, with the declarations that lost crossed out and the ones the browser could
not use flagged. Its Computed pane shows the final value of every property, in pixels and rgb(), beside a diagram
of the element’s box. You can change any value, or switch a declaration off, and the page updates at once.
This lesson works through a small card with three planted bugs, one overridden declaration, one invalid and one inactive, in Chrome, Firefox and Safari.
Opening DevTools
- Chrome: Ctrl+Shift+I or F12 opens DevTools, and Ctrl+Shift+C picks an element on the page; on a Mac, Cmd+Option+I and Cmd+Option+C.
- Firefox: Ctrl+Shift+I opens DevTools and Ctrl+Shift+C picks an element; on a Mac, Cmd+Option+I and Cmd+Option+C.
- Safari: Cmd+Option+I opens Web Inspector, after the one-time setting below; right-click an element and choose Inspect Element to pick it.
Right-clicking an element and choosing Inspect (Firefox’s documentation calls the item Inspect Element) works in Chrome and Firefox too, and it is the quickest way in, because the element you clicked is selected straight away. Safari hides its tools until you turn them on once: open Safari > Settings, go to Advanced and tick Show features for web developers.
Phones have no DevTools of their own, but a computer can inspect them over a cable. For an Android phone, turn on USB
debugging in the phone’s developer options, connect it, and open chrome://inspect#devices in Chrome on the computer.
For an iPhone, turn on Settings > Safari > Advanced > Web Inspector, connect it to a Mac, and choose the phone from
Safari’s Develop menu.
The practice card
Here is a small order card. Paste the two files into the HTML, CSS & JS Playground and inspect its preview as you read; the steps below find each bug.
HTML · card.html
<div class="panel">
<article class="card">
<h2 class="card-title">Order 4821</h2>
<p>Your parcel has left the Pune warehouse.</p>
<a class="button" href="#track">Track it</a>
</article>
</div> CSS · card.css
.panel {
font-size: 16px;
width: 640px;
}
.card {
font-size: 1.25em;
width: 50%;
padding: 0.75em 1em;
border: 0.1em solid #c8d3e0;
margin: 0 auto;
line-height: 1.5;
color: #243b53;
justify-content: center;
}
.card-title {
font-weight: 600;
colour: #102a43;
}
.card a {
background: #0b6e4f;
color: #ffffff;
padding: 0.25em 0.75em;
}
.button {
background: #1d4ed8;
} The design asked for three things that did not happen: a title darker than the text (#102a43), a blue “Track it”
button (#1d4ed8), and the card’s contents centred. None of the three shows an error anywhere on the page.
Reading the Styles pane
Select the “Track it” link. The Styles pane lists the rules that match it in cascade order, the strongest first:
declarations from the element’s style attribute (shown as element.style), then your rules, then the browser’s
default rules, and below them a section for each ancestor the element inherits from. Firefox labels the style
attribute’s declarations element {} and hides the browser’s default rules until you tick Show Browser Styles in
its settings.
What the Styles pane lists for the link, strongest first (schematic)
Text description of the diagram
A schematic of the Styles pane with the "Track it" link selected, listing from top to bottom the rules that apply to it, the strongest first. Each browser draws this differently; the order and the crossing out are what they share.
- element.style, the link's style attribute, which is empty here.
- The rule .card a from card.css, with specificity 0,1,1. It sets background: #0b6e4f, color: #ffffff and padding: 0.25em 0.75em, and it wins.
- The rule .button from card.css, with specificity 0,1,0. Its background: #1d4ed8 is crossed out, because .card a sets the same property with a more specific selector.
- The browser's default rule :link, whose color: #0000ee is crossed out, because .card a sets the colour.
- The browser's default rule :link, :visited, with text-decoration: underline and cursor: pointer, which still apply.
- A section headed "Inherited from article.card", showing the inherited declarations of the .card rule: font-size: 1.25em and line-height: 1.5, which the link inherits, and color: #243b53, crossed out because the link sets its own colour. Properties of .card that are not inherited, such as its width, are left out of this schematic; Chrome shows them in pale text.
The background in .button is crossed out: it lost in the cascade. Both rules set background, and .card a,
with one class and one element in its selector, is more specific than .button, with one class, so it wins even
though .button comes later in the file. Specificity is compared before order. In Chrome, hovering over a selector
shows its specificity; in Firefox, the funnel icon beside an overridden declaration filters the pane down to every rule
that sets that property, which shows the whole contest at once.
Now select the title. Its colour: #102a43 is flagged as invalid, because colour is not a CSS property, so the
parser dropped it, as the first lesson of this module showed. Chrome crosses the declaration out and adds a warning
icon, Firefox shows a warning icon, and Safari highlights it in yellow with a tooltip. The title therefore keeps the
colour it inherits from the card.
Finally select the card. Its justify-content: center is inactive: pale, with an information icon in Chrome and
Firefox. The declaration is valid and won the cascade, but it only does something on flex and grid containers, and the
card is an ordinary block. Hover over the icon and the browser says so. Inactive is a different problem from
overridden: nothing beat this declaration, it simply has no effect on this kind of box.
| What the Styles pane shows | What it means | Where to look next |
|---|---|---|
| A declaration crossed out | Another declaration for the same property won the cascade | The rule above it that sets the same property |
| A warning icon (Safari: a yellow highlight) | The parser could not use the property or the value | A misspelt name, a missing unit or semicolon |
| Pale text with an information icon | Valid and winning, but without effect on this box | The hint, then display or position |
| A rule missing from the list | Its selector does not match, or the file never loaded | The selector, then the Network panel |
Reading the Computed pane
The Styles pane shows what you wrote; the Computed pane shows what the browser ended up with. Its values are
resolved values, the same ones JavaScript reads with getComputedStyle(): lengths in pixels, colours as rgb(),
every shorthand split into its longhands, and inherited values included. For the card, the program below works out the
same numbers from card.css: an em length multiplies a font size, a percentage width takes the container’s width,
and auto margins share what is left.
// What the Computed pane shows for the .card element of card.html, worked out from card.css: an em length
// multiplies a font size, a percentage width takes the container's width, and an auto margin takes what is left over.
// The results are what getComputedStyle() returned for the card in Chromium 153, Firefox 155 and WebKit 26.6.
import { readFileSync } from "node:fs";
// The declarations of one rule, as { property: value } (card.css has plain rules, one declaration per line).
function declarations(css, selector) {
const block = css.split(`${selector} {`)[1].split("}")[0];
return Object.fromEntries(
block.split(";").map((d) => d.trim()).filter(Boolean).map((d) => {
const colon = d.indexOf(":");
return [d.slice(0, colon).trim(), d.slice(colon + 1).trim()];
}),
);
}
const css = readFileSync("card.css", "utf8");
const panel = declarations(css, ".panel");
const card = declarations(css, ".card");
const em = (value, fontSize) => parseFloat(value) * fontSize; // "1.25em" of 16px is 20px
const panelFont = parseFloat(panel["font-size"]); // 16px, inherited by the card
const panelWidth = parseFloat(panel.width); // 640px, the card's container
const fontSize = em(card["font-size"], panelFont); // the card's em is now its own font size
const width = (parseFloat(card.width) / 100) * panelWidth;
const [padTop, padSide] = card.padding.split(" ");
const padY = em(padTop, fontSize);
const padX = em(padSide, fontSize);
const borderValue = card.border.split(" ")[0];
const border = em(borderValue, fontSize);
// margin: 0 auto. The two auto margins share what the box leaves of the container's width.
const boxWidth = border + padX + width + padX + border;
const margin = (panelWidth - boxWidth) / 2;
const lineHeight = parseFloat(card["line-height"]) * fontSize;
const hex = card.color;
const rgb = `rgb(${[1, 3, 5].map((i) => parseInt(hex.slice(i, i + 2), 16)).join(", ")})`;
const rows = [
["font-size", card["font-size"], `${fontSize}px`],
["width", card.width, `${width}px`],
["padding-top", padTop, `${padY}px`],
["padding-left", padSide, `${padX}px`],
["border-top-width", borderValue, `${border}px`],
["margin-left", "auto", `${margin}px`],
["line-height", card["line-height"], `${lineHeight}px`],
["color", hex, rgb],
["justify-content", card["justify-content"], card["justify-content"]],
];
console.log("property you wrote Computed shows");
for (const [property, wrote, shows] of rows) console.log(`${property.padEnd(17)} ${wrote.padEnd(11)} ${shows}`);
console.log(`\nThe box: ${border} + ${padX} + ${width} + ${padX} + ${border} = ${boxWidth}px wide,`);
console.log(`starting ${margin}px from the left edge of the 640px panel.`); Output
property you wrote Computed shows font-size 1.25em 20px width 50% 320px padding-top 0.75em 15px padding-left 1em 20px border-top-width 0.1em 2px margin-left auto 138px line-height 1.5 30px color #243b53 rgb(36, 59, 83) justify-content center center The box: 2 + 20 + 320 + 20 + 2 = 364px wide, starting 138px from the left edge of the 640px panel.
Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node computed.mjs
Chromium 153, Firefox 155 and WebKit 26.6 returned exactly these values for the card from getComputedStyle(), and
laid its border box out 364px wide, 138px from the panel’s left edge. The box-model diagram (in Chrome, open the Styles
tab’s sidebar; in Firefox, the Layout view; in Safari, the top of the Computed panel) draws the same numbers:
content 320px wide, padding 15px and 20px, a 2px border, and margins of 0 and 138px.
Two values in the table deserve a second look. margin-left shows 138px, not auto: for margins, widths and
padding the resolved value is the one the browser used for layout. And justify-content shows center, although it
does nothing to this card. The Computed pane lists values, not effects, so only the Styles pane’s inactive hint tells
you that the value is wasted.
To find out where a computed value came from, expand the property. Firefox shows the rule that set the value, with a link to its file and line. Chrome lists every declaration that set the property, the winner first, each with its file, and adds an arrow that jumps to the declaration in the Styles pane.
Editing CSS live
Everything in the Styles pane can be changed, and the page follows each change at once:
- Click a value to edit it. While a number is selected, the Up and Down arrow keys change it by 1; in Chrome, Shift changes it by 10 and Alt (Option on a Mac) by 0.1.
- Untick the checkbox beside a declaration to switch it off without deleting it.
- Click inside a rule to add a declaration. Typing into
element.styleadds astyleattribute to the element. - Use .cls (Chrome, Firefox) to add or remove classes, and :hov (Chrome, Firefox) to force states such as
:hover,:focus-visibleand:active, so that you can inspect a hover style without holding the mouse still. Safari has a Classes button and toggles pseudo-classes from the element’s context menu. - Add a whole new rule with the + button; Safari puts new rules in an “Inspector Style Sheet”.
Use these on the card: rename colour to color, replace justify-content: center with text-align: center, and
make the button’s selector .card .button, which outranks .card a. All three bugs disappear.
Edits made in DevTools are lost when the page reloads, so copy them back into your files. Chrome’s Changes panel lists every edit as a diff and has a Copy button for CSS changes, Firefox’s Changes pane offers Copy Rule, and Safari’s Elements tab has a Changes panel too. Chrome can also save edits straight to your files on disk with Workspaces. Here is the corrected stylesheet:
.panel {
font-size: 16px;
width: 640px;
}
.card {
font-size: 1.25em;
width: 50%;
padding: 0.75em 1em;
border: 0.1em solid #c8d3e0;
margin: 0 auto;
line-height: 1.5;
color: #243b53;
text-align: center; /* was justify-content: center, which a block box ignores */
}
.card-title {
font-weight: 600;
color: #102a43; /* was colour, which is not a property */
}
.card a {
background: #0b6e4f;
color: #ffffff;
padding: 0.25em 0.75em;
}
.card .button {
background: #1d4ed8; /* was .button, which loses to the more specific .card a */
} Layout overlays
In the DOM tree, Chrome, Firefox and Safari put a small flex or grid badge next to every element that is a flex or grid container. Click the badge to draw an overlay on the page: the container’s lines, the gaps between items and, for grids, the numbered tracks and named areas. When a flex or grid layout misbehaves, switch the overlay on before you change any CSS. The flexbox and grid modules of this track use these overlays throughout.
Testing preferences and print
Many stylesheets have rules that apply only in certain conditions: a dark colour scheme, reduced motion, printing. DevTools can switch each condition on for one tab, without changing your computer’s settings, so you can test every branch:
/* Three conditions to test from DevTools without changing your computer's settings. */
@media (prefers-color-scheme: dark) {
.card {
color: #d9e2ec;
background: #102a43;
border-color: #334e68;
}
.card-title {
color: #f0f4f8;
}
}
@media (prefers-reduced-motion: no-preference) {
.card .button {
transition: background-color 0.2s;
}
}
@media print {
.card .button {
display: none;
}
} - Chrome: open the Rendering tab (press Ctrl+Shift+P, or Cmd+Shift+P on a Mac, type “rendering” and choose Show Rendering; or use More tools > Rendering), then choose a value under “Emulate CSS media feature prefers-color-scheme”, the same for prefers-reduced-motion, forced-colors and prefers-contrast, or “print” under “Emulate CSS media type”. The Styles pane also has a shortcut button for light and dark.
- Firefox: the Rules view’s toolbar has buttons for the light and dark colour schemes and for print media.
- Safari: the Elements tab has a Force print media styles button and an Override user preferences popover for colour scheme, reduced motion and increased contrast.
Switch on the dark scheme and the card’s text and background change; switch on print and the button disappears.
Try it: fix the card yourself
Start again from the original card.css in the Playground and, without looking at the fixes above, work through the
three problems one at a time: select the element, find the declaration responsible in the Styles pane, name what kind
of problem it is (overridden, invalid or inactive), and fix it live. Then copy your changes out, paste them over the stylesheet, and
check that the card still looks right after the preview reloads. The quiz below has five more elements to diagnose.
Check yourself
6 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.
Interview questions
What does the Computed pane in DevTools show that the Styles pane does not?
The Styles pane shows the rules and declarations as they are written in your stylesheets, in cascade order, including
the ones that lost, so it explains why a value won. The Computed pane shows the result: one final value for every
property of the selected element, as resolved values, the same ones getComputedStyle() returns. Relative units become
pixels, colours become rgb(), shorthands are split into longhands, inherited values are included, and margins, widths
and padding show the sizes used for layout, so margin: auto reads as 138px. Expanding a property shows which
declaration set it. Use Computed to see what the browser actually used; use Styles to see why.
Key takeaways
- Right-click and Inspect opens the Elements panel on that element; Safari first needs “Show features for web developers” in its settings.
- The Styles pane lists matching rules strongest first: crossed out means overridden, a warning means invalid, pale with an information icon means valid but inactive.
- The Computed pane shows resolved values, the same as
getComputedStyle(): pixels,rgb(), longhands and the margins used for layout, but values, not effects. - Edit, toggle and add declarations live, then copy them back from the Changes panel, because a reload discards them.
- Flex and grid badges draw layout overlays, and emulation tests dark mode, reduced motion and print without changing your settings.
References
- View and change CSS (Chrome DevTools) (Google (Chrome for Developers))
- Find invalid, overridden, inactive, and other CSS (Chrome DevTools) (Google (Chrome for Developers))
- CSS features reference (Chrome DevTools) (Google (Chrome for Developers))
- Emulate CSS media features (Chrome DevTools) (Google (Chrome for Developers))
- Remote debug Android devices (Chrome DevTools) (Google (Chrome for Developers))
- Examine and edit CSS (Firefox DevTools) (Mozilla)
- Keyboard shortcuts (Firefox DevTools) (Mozilla)
- Enabling Web Inspector (WebKit)
- Elements Tab (Web Inspector Reference) (WebKit)
- CSS Object Model (CSSOM), resolved values (W3C CSS Working Group)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress