---
title: 'Element and Style Inspection'
source: 'https://academia.sh/en/courses/browser-platform/element-and-style-inspection'
course: 'The Browser and the Web Platform'
language: en
updated: '2026-08-17T18:09:11+00:00'
license: 'CC BY-SA 4.0'
---

# Element and Style Inspection

The question the Elements panel answers; the difference between the live tree and the source text, the declared-computed-used value distinction, reading cascade order, tracing inheritance, and interpreting box measurements.

The layers built up to this point become invisible when they work together. When
something goes wrong, the source does not show which layer the problem comes from: a
measurement badge's color could be wrong because a style rule won unexpectedly, or its
number could be stale because the request was served from cache.

This topic covers the browser's built-in diagnostic tools by the questions they ask. Each
tool answers a specific question, reads a specific measure, and interprets it a specific
way. The first tool is closest at hand: the **Elements panel**, which shows what values
an element is actually rendered with.

## The Tree the Panel Shows

The tree in the panel is not a picture of the text that came from the server. The
distinction from the DOM API lesson is reflected in the tool here: what is shown is the
**live tree** resulting from parsing and every script that has run up to that moment.

Four sources produce this difference. The parser's recovery rules complete missing
closing tags and move misplaced elements, so a structure not in the source appears in
the tree. Scripts add and remove nodes. Shadow trees are shown as separate subtrees, not
present in the document tree. Pseudo-elements are not nodes in the tree, but because they
render, they are marked as separate rows in the panel.

A direct diagnostic rule follows from this: **someone who cannot find the structure they
see in the panel in the source is looking at two different things.** What the server
actually sent is a separate question, and it is the subject of the Network panel.

## Three Values: Declared, Computed, Used

The most common confusion in style inspection is the same property appearing differently
in different places. The reason: a value passes through three stages on its way to
rendering.

The **declared value** is exactly as written in the style file: `1.5rem`, `50%`,
`inherit`, `auto`. The winning declaration is listed in this form.

The **computed value** is the value after inheritance and relative units have been
resolved. A unit defined relative to text size drops here to an absolute length; keywords
are converted to their own equivalents. Inheritance carries the value at this stage.

The **used value** is the number that emerges once the layout calculation has finished.
Percentages are resolved against the container's real width, `auto` drops to a number,
and flexing and grid distribution give their result here.

The distinction is decisive in diagnosis. When an element whose width is written as a
percentage renders narrower than expected, the computed value can still show a
percentage; the problem is in the container's width and only shows up in the used value.
Conversely, if a color declaration is never applied, the used value is misleading; the
place to look is cascade order.

## The Rule of Ordering and the Struck-Through

The style list orders every declaration matching an element from winner to loser and
strikes through the losers. Struck-through lines are not noise, they are the diagnosis
itself: why a rule was not applied is read from where the winning rule sits.

The computation producing the order rests on three criteria.

```js
// cascade.mjs — the computation producing the style list's order and the winning declaration
// Specificity triple: (id, class/attribute/pseudo-class, type/pseudo-element)
function specificity(selector) {
  const parts = selector.split(/\s+/);
  let a = 0, b = 0, c = 0;
  for (const p of parts) {
    a += (p.match(/#[\w-]+/g) ?? []).length;
    b += (p.match(/\.[\w-]+|\[[^\]]+\]|:(?!:)[\w-]+/g) ?? []).length;
    c += (p.match(/::[\w-]+/g) ?? []).length;
    if (/^[a-zA-Z][\w-]*/.test(p)) c += 1;
  }
  return [a, b, c];
}

// Layer weight: the larger one wins.
const WEIGHT = { "browser": 0, "author": 1, "author!": 2, "browser!": 3 };
const weight = (b) => WEIGHT[b.source + (b.important ? "!" : "")];

function order(declarations) {
  return [...declarations].sort((x, y) => {
    if (weight(x) !== weight(y)) return weight(y) - weight(x);
    const ox = specificity(x.selector), oy = specificity(y.selector);
    for (let i = 0; i < 3; i++) if (ox[i] !== oy[i]) return oy[i] - ox[i];
    return y.order - x.order; // written later wins
  });
}

// color declarations applied to a span.value element
const declarations = [
  { order: 1, source: "browser", selector: "span", value: "canvastext", important: false },
  { order: 2, source: "author", selector: ".value", value: "#333", important: false },
  { order: 3, source: "author", selector: "#dashboard .value", value: "#0a5", important: false },
  { order: 4, source: "author", selector: "span.value", value: "#b23", important: false },
  { order: 5, source: "author", selector: ".value", value: "#07c", important: false },
  { order: 6, source: "author", selector: ".warning .value", value: "#c30", important: true },
];

console.log("color declarations (winner on top):");
for (const [i, b] of order(declarations).entries()) {
  const [a, s, t] = specificity(b.selector);
  console.log(
    `  ${i === 0 ? "→" : " "} ${b.selector.padEnd(16)} ${b.value.padEnd(12)}` +
    `specificity ${a}-${s}-${t}  ${b.source}${b.important ? " !important" : ""}` +
    `${i === 0 ? "" : "   (struck through)"}`);
}

// Inheritance: only kicks in when no declaration matches at all.
const winner = (list) => (list.length ? order(list)[0].value : null);
const parentColor = winner(declarations);
const childDeclarations = []; // no color rule matches the strong element

console.log("\nspan.value  → computed color:", parentColor);
console.log("strong      → computed color:",
  winner(childDeclarations) ?? `${parentColor} (inherited)`);
```

```
color declarations (winner on top):
  → .warning .value  #c30        specificity 0-2-0  author !important
    #dashboard .value #0a5        specificity 1-1-0  author   (struck through)
    span.value       #b23        specificity 0-1-1  author   (struck through)
    .value           #07c        specificity 0-1-0  author   (struck through)
    .value           #333        specificity 0-1-0  author   (struck through)
    span             canvastext  specificity 0-0-1  browser   (struck through)

span.value  → computed color: #c30
strong      → computed color: #c30 (inherited)
```

Three criteria are at work in the list, and which one takes effect is read by comparing
lines.

The first line shows **weight beating specificity**: a declaration with lower specificity
sits at the top only because it is marked important. Before answering the question of why
a rule won with "because it is more specific," the weight column is checked.

The second and third lines show that the **specificity comparison is lexicographic**: if
the id count is not equal, the rest is not counted at all. One id beats however many
classes are written.

The fourth and fifth lines show that **source order** is the last criterion: between two
declarations of the same selector, the one written later wins. This is where
file-loading order enters the diagnosis when the same class is defined in two files.

The last line shows the browser's own default style appears in the list and always stays
at the bottom. For a property the author has no rule for, this line wins; the source of
an unexpected margin is most often here.

## Reading Inheritance

The output's last two lines show inheritance's condition: when no declaration matches an
element and the property is an inherited one, the value is taken from the parent's
computed value.

In the panel, this appears as a value with no source in the element's own rules, shown
next to the ancestor it belongs to. The diagnostic rule is: **changing an inherited value
requires writing a rule on the element itself; a rule written on the ancestor never
appears if another matching declaration sits in between.** Inheritance is not part of the
cascade, it is the fallback that kicks in when the cascade produces no result.

## Reading Box Measurements

The box measurements view shows an element's content area, padding, border, and margin as
numbers. Every number here is a **used value**: percentages resolved, `auto` dropped to a
number, colliding margins merged.

Three readings come up often. Content width differing from what was written shows the
box-sizing model includes padding and border in the width. A margin coming out at half of
what was written shows collapsing with the neighboring box has occurred. Zero height
shows the element takes up no space in flow — that its children are floated or absolutely
positioned.

An element not being rendered at all is a separate case, read not in the box measurements
but in the computed values: which of display type, visibility, opacity, and clipping is
in effect is tested separately. All four remove the element from view, but three preserve
its place, one does not.

## Forcing States

Rules applied while the pointer is over an element or it has focus mostly disappear the
moment inspection is wanted: the pointer leaves the element, focus moves to the panel.
For this reason, inspectors allow **forcing** an element's state; the relevant
pseudo-class rules are applied and appear in the list even though the element is not
actually in that state.

The same route serves accessibility auditing: whether the focus indicator is actually
visible is tested by forcing the focus state. The requirement from the Focus and
Keyboard Interaction lesson becomes measurable here.

Pseudo-elements are listed separately because they have their own rule sets and do not
appear in the parent's list. Why generated content is not showing up is mostly answered
there.

## Summary

- The tree in the panel is the live tree: parser recovery, script changes, shadow trees,
  and pseudo-elements diverge from the source text.
- A value passes through the declared, computed, and used stages; which stage to look at
  depends on the kind of question.
- The style list orders declarations by weight, specificity, and source order;
  struck-through lines say why a rule lost.
- The specificity comparison is lexicographic: if a higher digit is not equal, the lower
  ones are not counted at all.
- The numbers in box measurements are used values; deviations from the written value are
  explained by the box model, collapsing, and flow behavior.
- Interaction states are inspected by forcing them; pseudo-element rules are listed
  separately.

## Next Step

This lesson answered why an element looks the way it does, not why its content is what it
is. If the number on the badge is stale, the problem is not in the style: either the
request was never made, it was served from cache, the server returned stale data, or the
response arrived much later than expected. The four are separated by different measures,
all read in one place: when the request started, how long it waited at which stage, and
which headers it came back with. The next lesson covers these measures and the causal
links between them.
