Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

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.

  • Beginner
  • 25 minutes
  • Examples run with Node.js 24.21.0
  • By MySmartCoPilot
Free: 25 minutes

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.

An order card with three bugs

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;
}
HTML, CSS & JS Playground (Online Editor) Paste card.html and card.css into its panes, then right-click the preview and inspect it.

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.

Schematic Styles pane for the card link: the winning .card a rule first, then .button with its background crossed out, then defaults.Styles pane: the "Track it" linkelement.style {style attribute}.card a {card.css, wins: 0,1,1background: #0b6e4f;color: #ffffff;padding: 0.25em 0.75em;}.button {card.css, loses: 0,1,0background: #1d4ed8;}:link {browser defaultcolor: #0000ee;}:link, :visited {browser defaulttext-decoration: underline;cursor: pointer;}Inherited from article.card.card {card.cssfont-size: 1.25em;line-height: 1.5;color: #243b53;}

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.

  1. element.style, the link's style attribute, which is empty here.
  2. 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.
  3. 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.
  4. The browser's default rule :link, whose color: #0000ee is crossed out, because .card a sets the colour.
  5. The browser's default rule :link, :visited, with text-decoration: underline and cursor: pointer, which still apply.
  6. 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 JavaScript · computed.mjs
// 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.style adds a style attribute to the element.
  • Use .cls (Chrome, Firefox) to add or remove classes, and :hov (Chrome, Firefox) to force states such as :hover, :focus-visible and :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:

The card's stylesheet with the three fixes CSS · card-fixed.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;
  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:

Rules for three conditions CSS · card-modes.css
/* 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.

  1. Question 1 of 6 A paragraph <p class="alert"> has no padding. Which declaration does the Styles pane show crossed out?

    Read the code, then choose one answer.

    p.alert {
      padding: 0;
    }
    
    .alert {
      padding: 1rem;
    }
    Show the answer to question 1

    Answer: padding: 1rem in .alert, because p.alert is more specific

    p.alert has a class and an element, .alert only a class, so p.alert wins and padding: 1rem is crossed out as overridden. Order of appearance only decides between declarations of equal specificity.

  2. Question 2 of 6 The links inside <nav class="menu"> are not centred vertically. What does the Styles pane show for align-items?

    Read the code, then choose one answer.

    .menu {
      display: block;
      align-items: center;
      min-height: 3rem;
    }
    Show the answer to question 2

    Answer: Pale text with an information icon, because align-items has no effect on a block box

    align-items is valid and nothing overrides it, but it only works on flex and grid containers. Chrome and Firefox mark such a declaration as inactive and explain why when you hover over the icon. Making the menu a flex container (display: flex) puts it to work.

  3. Question 3 of 6 The price should be red but shows in the default text colour. What does the Styles pane show for the second declaration?

    Read the code, then choose one answer.

    .price {
      font-weight: 700;
      colour: #b00020;
    }
    Show the answer to question 3

    Answer: A warning that the property is invalid, because colour is not a CSS property

    The parser drops a declaration whose property it does not know. Chrome crosses it out with a warning icon, Firefox shows a warning icon and Safari highlights it in yellow. font-weight still applies.

  4. Question 4 of 6 You wrote a rule for an element, but it does not appear in the element's Styles pane at all. Which of these could explain it?

    Choose every answer that is right.

    Show the answer to question 4

    Answer:

    • The stylesheet that contains the rule never loaded
    • The rule's selector does not match the element

    The Styles pane lists every rule that matches the element, including rules whose declarations all lost (they are shown crossed out) and rules with invalid values (they are flagged). A rule that is missing altogether either does not match, so check the selector and the element's classes, or never reached the page, so check the Network panel for the stylesheet.

  5. Question 5 of 6 You select <h2 class="title" style="color: black">Orders</h2>. What does the Styles pane show?

    Read the code, then choose one answer.

    .title {
      color: navy;
    }
    Show the answer to question 5

    Answer: color: black under element.style at the top, and color: navy in .title crossed out

    Declarations in a style attribute are listed first, as element.style (Firefox: element {}), because they beat every selector rule of the same importance. color: navy therefore loses and is crossed out.

  6. Question 6 of 6 An element has width: 200px, padding: 0.5em and border: 1px solid, with font-size: 20px and the default box-sizing: content-box. How wide is its border box in the box-model diagram?

    Type a number.

    Show the answer to question 6

    Answer: 222 px

    With content-box sizing, width is the content alone. The padding is 0.5 × 20px = 10px on each side and the border 1px on each side, so the border box is 200 + 10 + 10 + 1 + 1 = 222px.

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

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.