Lesson 20 / 24
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.
Contents
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.
// 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.
To keep your progress and take notes, Log in
My notes
Log in to take notes.