CSSGENERALENGINE-SPECIFICBROWSER-SPECIFIC

Inheritance and Computed Style

From declared value to cascaded, specified, computed, used and actual value — which properties inherit, what inherit, initial, unset and revert each mean, and why reading style back can force layout.

The intent, the obvious build, and why it breaks

Every lesson starts where the work starts: someone wanted an outcome, and the first implementation that comes to mind has a problem.

The question

After the cascade picks a winner, what does the browser actually store on the element — and why is that not the number I see on screen?

The user intent

Someone sets a font on a container and expects the text inside it to follow. Then they set a width in percent and expect to be able to read it back, and get a percentage string, or a pixel number, depending on which API they asked.

The obvious build

The cascade picks a value and the element has it. getComputedStyle tells me what it is, and inheritance means children get whatever the parent has.

Why it breaks

Most properties do not inherit. color, font-*, line-height, visibility, cursor and the text properties do; background, border, padding, margin, display, width and almost everything geometric do not. Expecting border to inherit is a genuinely common first bug.

How it breaks in a real browser
  • Most properties do not inherit. color, font-*, line-height, visibility, cursor and the text properties do; background, border, padding, margin, display, width and almost everything geometric do not. Expecting border to inherit is a genuinely common first bug.
  • The value stored on the element is not the value you wrote. font-size: 1.5em is stored as an absolute length; color: currentColor is stored as the resolved colour; a relative URL is stored as absolute.
  • It is also not the value on screen. width: 50% computes to 50% and only becomes a pixel count at layout time, when there is a containing block to be half of.
  • getComputedStyle is not one thing. For some properties it returns the computed value and for others the *used* value, which means the same call can be free for one property and force a synchronous layout for another (Layout Thrashing).
  • It has no account of inherit, initial, unset and revert, which are four different answers to "make this go away" and are routinely used interchangeably.
IntentEventStateUI LogicDOM WorkNetworkLayout / PaintPixelsFeedback

What is actually happening

In the browser, not in the framework.

  • Declared → cascaded. Every declaration that applies is declared; the cascade picks at most one winner per property. That winner is the cascaded value (The Cascade).
  • Cascaded → specified. If there is no cascaded value, defaulting fills one in: the inherited value from the parent for inherited properties, the initial value for the rest. This is where inheritance actually happens — it is a *defaulting* step, not a copying step.
  • Specified → computed. Relative things are resolved as far as they can be without layout: em and rem become absolute lengths, keywords like bolder become numbers, relative URLs become absolute, currentColor resolves. This is also where var() substitution happens (Custom Properties).
  • Computed → used. Anything that needed layout is resolved now: percentages against a containing block, auto margins, flex and grid track sizes (Intrinsic Sizing and the Automatic Minimum).
  • Used → actual. The final adjustment for the device: rounding to physical pixels, font fallback when the requested face is unavailable (The Viewport and Device Pixels).
  • The four global keywords act at the specified-value step. inherit takes the parent's computed value even for a non-inherited property; initial takes the property's spec-defined initial value, which is often not what the browser's own stylesheet uses; unset means inherit for inherited properties and initial for the rest; revert rolls back to what the previous cascade origin would have produced — usually the user-agent stylesheet — and revert-layer does the same for the previous layer.
  • Inheritance flows down the element tree, not the visual layout. A positioned element inherits from its DOM parent no matter where it renders (Positioning and Stacking Contexts).

What this makes the browser do

And which of it is avoidable.

  • Computing and storing one computed value per property per element, for every element in the document. This is the memory footprint of style, and it is proportional to element count.
  • Sharing that work aggressively: engines cache computed styles for elements with identical matched rules and identical inherited context, so a list of a thousand identical rows does not run the pipeline a thousand times (Style Calculation).
  • Propagating inherited changes down the subtree. Changing an inherited property near the root — color, font-size — invalidates every descendant, which is the single largest style invalidation an application performs (Style Invalidation).
  • Flushing pending style and layout when script asks a question that needs an answer geometry can provide. That flush is synchronous and it is on the main thread.
  • The avoidable half: reads interleaved with writes. Every alternation forces another flush, turning a linear operation into a quadratic one.

One value, six stages

The value you write and the value on screen are separated by more steps than most people carry around, and nearly every confusing CSS result is a step boundary. Percentages that will not resolve, em sizes that compound, a reset that gives inline instead of block — each is a specific stage doing exactly what it is defined to do.

Walk the stages in order when something is wrong. The question "at which stage does this value stop being what I wrote" is answerable, and it usually takes one look at the Computed pane.

Declared to actual
  1. 1
    Declared

    Every declaration in every stylesheet that applies to this element and property.

    fails by A declaration dropped at parse time for an unsupported value, so it was never declared at all (The CSSOM).

  2. 2
    Cascaded

    The cascade picks at most one winner, by origin, context, element-attached, layer, specificity, order.

    fails by Losing at a step you were not looking at (The Cascade).

  3. 3
    Specified

    If nothing was cascaded, default: inherit from the parent for inherited properties, initial value otherwise. Global keywords act here.

    fails by Expecting border or padding to inherit. They do not; only their initial values apply.

  4. 4
    Computed

    Resolve everything possible without layout: em/rem to lengths, keywords to numbers, currentColor, relative URLs, var() substitution.

    fails by font-size in em compounding through nested elements of the same class, because each level computes against its parent's computed size.

  5. 5
    Used

    Resolve what needed layout: percentages against a containing block, auto, flex and grid track sizes.

    fails by A percentage height with no definite containing-block height, so it resolves to auto and appears to be ignored (Intrinsic Sizing and the Automatic Minimum).

  6. 6
    Actual

    Device adjustments: rounding to physical pixels, font fallback for a face that is not available.

    fails by Sub-pixel rounding producing a one-pixel seam between adjacent elements (The Viewport and Device Pixels).

The computed/used boundary is the important one. Everything before it happens during style calculation; everything after it needs layout, which is why reading a used value can force one.

Inheriting, and the four ways to undo

Inheritance is a defaulting rule, not a copy: when an element has no cascaded value for an inherited property, it takes the parent's *computed* value. That "computed" matters — the child inherits an already-resolved length, not the em expression that produced it.

The four global keywords are the escape hatches, and they are not interchangeable. The table below is the one to memorise, because the difference between initial and revert is the difference between display: inline and display: block on a div, and that has ended more debugging sessions than it should.

KeywordWhat the element getsUse it whenThe trap
inheritThe parent's computed value, even for a non-inherited propertyYou want a border or a background to follow an ancestor deliberatelyOn the root element there is no parent, so it behaves as initial
initialThe property's spec-defined initial valueYou want the CSS-defined default, independent of any browserdisplay: initial is inline, and color: initial is usually black, not the browser default text colour
unsetinherit if the property inherits, initial if it does notYou want "as if no rule had been written"Still lands on spec initials, so it inherits display: inline on a div too
revertWhat the previous cascade origin would have produced — usually the user-agent stylesheetYou want "the browser's normal behaviour back"The user-agent stylesheet is not standardised, so this is a per-browser value
revert-layerWhat the previous cascade layer would have producedUndoing one layer's contribution without falling all the way backMeaningless outside a layer; falls back to revert behaviour
all: revertEvery property reverted at onceEstablishing a clean boundary around embedded or third-party markupRemoves your intentional typography too, so you re-establish it inside
Inheritance used deliberately
1:root {
2 /* Set inherited properties once. Everything below follows. */
3 color-scheme: light dark;
4 color: canvastext;
5 font-family: system-ui, sans-serif;
6 line-height: 1.5; /* unitless: recomputed per descendant font size */
7 /* NOT set: font-size. Leave the root at the browser default so a user
8 who raised their default text size actually gets larger text. */
9}
10
11.icon {
12 /* Inherits by proxy: color already inherits, so one declaration keeps
13 the icon in step with its text, including in forced-colors mode. */
14 fill: currentColor;
15 block-size: 1em; /* computed against this element's font-size */
16}
17
18.embed-boundary {
19 /* Third-party markup should not inherit our typography. */
20 all: revert;
21 /* ...then re-establish anything it genuinely needs. */
22}
23
24/* The compounding trap: each level computes against its PARENT's
25 already-computed size, so .tag .tag .tag shrinks three times. */
26.tag { font-size: 0.875em; }
27
28/* Fixed against the root instead, so nesting does not compound. */
29.tag { font-size: 0.875rem; }

The two .tag rules differ by one character and by whether nesting changes the result. em is relative to the parent; rem is relative to the root.

Hiding, and what it does to the accessibility tree

The most consequential computed-style decision in most applications is which of several ways to hide something you pick, because they differ in whether the content stays in the accessibility tree and whether it stays focusable. Getting this wrong produces a control that a sighted mouse user cannot see and a keyboard user lands on.

The spec is unambiguous here, so this is a matter of knowing the table rather than of judgement: display: none and visibility: hidden remove from layout and from the tree; opacity: 0 and off-screen positioning remove from *sight* only.

Reads and writes around computed style
ChangestylelayoutpaintcompositeWhy
`getComputedStyle(el).color`yesnononoFlushes pending style only. color is fully resolved at computed-value time, so no geometry is needed.
`getComputedStyle(el).width`yesyesnonoA used value. The browser must finish layout before it can answer with a number.
`el.getBoundingClientRect()`yesyesnonoAlways a used-value read. Cheap once per frame, ruinous once per list item (Layout Thrashing).
Change `color` on `:root`yesnoyesyesInherited, so every descendant is invalidated — but no geometry changed, so layout is skipped.
Change `font-size` on `:root`yesyesyesyesInherited *and* geometric. Every descendant restyles and the whole document relayouts. The most expensive one-line change in CSS.
Toggle `display: none` on a subtreeyesyesyesyesBoxes are destroyed and everything after it reflows. Also removes the subtree from the accessibility tree.
Toggle `visibility: hidden` on a subtreeyesnoyesyesInherited, and the box is preserved, so surrounding geometry does not move. Still removed from the accessibility tree.

caveat The layout column collapses to no for a subtree under contain: layout or content-visibility, because the browser can prove nothing outside it moved (CSS Containment). Everything here also depends on what the engine had already marked dirty when your code ran.

accessibility specVisually hidden text and hidden regionsHiding content correctly

semantics The element keeps whatever role its markup gives it. What changes is whether it is exposed at all: display: none and visibility: hidden remove the node from the accessibility tree; clipping to a tiny box does not; aria-hidden="true" removes it from the tree while leaving it visible and focusable.

TabReaches anything not removed by display: none, visibility: hidden, the hidden attribute, inert, or a negative tabindex — including elements at opacity: 0 or positioned off-screen.
Tab (skip link)The canonical case for visually-hidden: the link is clipped until focused, then :focus-visible restores its box so a sighted keyboard user can see where they are.
EscapeCloses a hidden-by-collapse region. Whatever the close path is, the hidden state must be the same one CSS reads and assistive technology reads.
Focus
  • Never leave focus on an element you just hid. The browser moves focus to the body, and the keyboard user loses their place entirely (Focus Management).
  • Use inert on a subtree that should be neither focusable nor announced while remaining visible — a background behind a modal. It is the correct primitive and it does not touch computed style.
  • A visually-hidden element that becomes visible on focus must have a real, visible focus indicator when it does.
Announces
  • A region hidden with display: none is simply absent; screen readers do not announce it and do not count it in landmark or heading lists.
  • A visually-hidden label or hint is announced normally, which is exactly why the pattern exists.
  • Toggling display on a live region defeats it: the region must be present in the tree before the content changes for the change to be announced (Live Regions and Announcement).

usually broken by The pattern invites hiding with opacity: 0, visibility on an ancestor, or off-screen positioning and assuming the content is gone. Two of those leave the content focusable and announced, so a keyboard user tabs into an invisible dropdown and a screen-reader user hears a menu that is not on screen. Decide explicitly which of the three states you want — hidden from everyone, hidden from sight only, or hidden from assistive technology only — and use the mechanism that matches.

How to build it

Most important first.

  • Set inherited properties once, high up. Typography, colour and text rendering belong on :root or a layout container; repeating them per component is bytes plus a wider invalidation surface for no benefit (Responsive Typography).
  • Reach for revert rather than initial when you want "the browser's normal behaviour back". display: initial is inline, which is almost never what someone removing a rule from a div wanted; display: revert gives block.
  • Use all: revert on a container to escape an inherited context wholesale — useful when embedding third-party markup you do not want your typography to reach into.
  • Batch all your style reads before all your style writes. One flush at the start of a frame is fine; twenty alternating ones is the classic layout thrash (Layout Thrashing).
  • Prefer currentColor for borders, outlines and icon fills. It inherits by proxy — the element already inherits color — so one declaration keeps a whole component in step, including in forced-colors mode (Contrast, Colour and Motion).
  • Do not read layout to make a decision that CSS can express. getBoundingClientRect in a resize handler where a container query would do is a forced layout you chose to pay for (Container Queries).

Keyboard, focus, semantics, announcement

A required field on every lesson in this domain, not a section added when there is room.

  • display: none and visibility: hidden remove content from the accessibility tree entirely, along with the layout box. opacity: 0, clip-path, and moving something off-screen do not — the content stays focusable and announced, which is how keyboard users end up tabbing into an invisible menu (The Accessibility Tree).
  • That asymmetry is the basis of the visually-hidden pattern: content clipped to a one-pixel box is still in the tree, so a skip link or a form hint can be available to a screen reader without being drawn (Semantics Before ARIA).
  • aria-hidden="true" removes from the accessibility tree while leaving the element visible and focusable. Combining it with a focusable element produces a control a screen-reader user can land on and receives no name for (The Rules of ARIA).
  • Setting a root font-size in px overrides the user's browser font-size preference for everything that inherits from it. Keep the root at the browser default and size in rem, so text scaling works (Responsive Typography).
  • Unitless line-height inherits correctly through nested elements with different font sizes; a fixed length does not, and the result is overlapping text for anyone who scales up. This is a real accessibility consequence of a computed-value rule.

What can go wrong

Failure modes
  • Setting font-size in em on nested elements of the same class, so the size compounds with depth. Each level computes against its parent's already-computed size.
  • Using a unitless line-height versus a length: the unitless value inherits as a *number* and is recomputed per descendant against that descendant's font size; a length inherits as a fixed length and silently crops text in nested elements with larger fonts.
  • Assuming display: none still runs your styles. No boxes are generated at all, so anything depending on measured geometry inside a hidden subtree reads zero (Intrinsic Sizing and the Automatic Minimum).
  • A getComputedStyle call inside a loop over list items — one forced layout per iteration, which is the most common way a smooth interaction becomes a visible stall (Long Tasks).
  • The mitigation failing: caching a computed value and reusing it after the DOM changed, so you now render against a stale measurement, which is worse than the slow version because it is wrong rather than slow.
Security
  • Computed style is readable by any script in the document, which makes it a side channel: historically :visited link styling leaked browsing history through getComputedStyle, and browsers now deliberately lie about visited-link styles for exactly that reason (The Browser Security Model).
  • That mitigation is itself a lesson in what the browser will enforce: the value you get back is not always the value in use, because privacy won over accuracy.
  • Inherited properties cross into content you did not author. Embedded third-party markup inherits your typography and colour, which is a rendering surprise rather than a vulnerability — but a container with all: revert is the honest boundary (Third-Party Scripts and the Supply Chain).
  • Nothing here isolates anything. If you need a subtree whose styles cannot be affected by the page, that is shadow DOM, and even then inherited properties still cross the boundary by design (Shadow DOM and the Composed Tree).
Misreads
  • "Everything inherits." Most properties do not; the inherited set is roughly the text-related ones plus a handful of others, and the spec lists it per property.
  • "unset and revert are the same." unset goes to inherit-or-initial; revert goes back to what the previous origin — usually the user-agent stylesheet — would have said. For display on a div those are inline and block.
  • "getComputedStyle is a cheap read." It is a *synchronising* read. For layout-dependent properties it forces the browser to finish work it had deferred.
  • "Computed style is what I see." The used value is what you see. Computed is the last stage before layout gets involved.
  • "display: none just hides it." It removes the element from layout *and* from the accessibility tree, which is why it is the correct way to hide something and the wrong way to visually hide something you still want announced.

Measuring it, and what changes in the field

How you would see this
  • The Computed pane shows the final computed value for every property, with an expandable trace of the declarations it beat and the ancestor an inherited value came from (A Mental Model of the Devtools).
  • Forced synchronous layouts appear in the Performance panel as their own marked entries, usually with a warning triangle and a stack pointing at the exact read (Debugging Rendering and Jank).
  • Recalculate Style entries name the number of elements affected, which is how you find an inherited-property change near the root that is invalidating far more than you expected.
  • For text scaling, the cheapest test is the browser's own font-size setting plus page zoom, not a devtools emulation — they exercise different code paths.
Slow device, slow network, large data, old tab
  • On a large document, changing an inherited property on :root invalidates every element. The same change on a leaf invalidates one. The declaration is identical; the cost differs by four orders of magnitude (CSS Containment).
  • On a slow device, the cost of a forced layout is proportional to how much geometry is dirty, so the same interleaved read-write loop that is imperceptible on a laptop can drop frames on a mid-range phone.
  • With a large list, computed style memory becomes real: every rendered row stores a full set of values, which is one of the arguments for virtualisation (List Virtualization).
  • Under user font scaling or page zoom, everything sized in em, rem or ch moves and everything in px does not — so a layout mixing both breaks in a way it never does at default settings.
What this costs
  • Relying on inheritance keeps stylesheets small and makes global changes trivial. It also means a component's appearance depends on where it is mounted, which is exactly the property that makes a design system hard to test in isolation (What a Component Owes Its Caller).
  • all: revert on a boundary gives you predictable isolation and throws away every intentional inherited value, so you must re-establish typography inside.
  • Reading computed style is the only way to know what the browser actually did. It is also the operation most likely to cost you a frame, so the accurate answer and the fast path are in tension.
  • Sizing everything in relative units makes text scaling work and makes precise alignment harder, because everything moves at once.

Where this applies

Frontend advice ages badly and fragments across engines. These labels say what each claim is specific to, and where a different browser, device or framework would differ.

  • GENERALThe six value stages, the inherited-property set and the meaning of the four global keywords are all specified in CSS Cascading and Inheritance, and engines agree. Where they historically differed — which properties getComputedStyle resolves to used values — has largely converged.
  • ENGINE-SPECIFICThe user-agent stylesheet is not standardised beyond a non-normative sample, so revert lands on slightly different values across engines: default form-control appearance, margin on headings and the exact rendering of <hr> all differ between Blink, Gecko and WebKit. revert gives you "the browser's default", which is a per-browser answer.
  • BROWSER-SPECIFICOnly some browsers flag forced synchronous layout in the profiler: Chrome marks it explicitly with a warning and a causing stack, Firefox surfaces it less directly in its profiler markers, and Safari does not label it as such — so the same code looks like a diagnosable problem in one browser and like unexplained time in another.

Where the depth lives

This domain teaches the browser-side mechanism and hands the rest off.

Securityxss
Domains that do not exist yet
  • Programming Languages & Runtime Internals — lazy evaluation and forced materialisation: getComputedStyle is a read that collapses deferred work, and the interleaving cost is the same shape as any pull-based pipeline forced element by element.