The CSS Object Model (CSSOM) is a set of interfaces for representing and working with CSS stylesheets, rules, and declarations in the browser. It lets scripts inspect or modify styling information and request the resolved styles of an element.

Between a declaration such as width: 50% and the result on screen, the browser must determine which declarations apply, resolve competing values, handle inheritance, and calculate layout. The value an author writes is therefore not always the value a developer tool displays—or a complete explanation of what the reader sees.

Understanding these steps makes CSS easier to debug. It helps distinguish a declaration that lost in the cascade from a winning value that depends on layout, a browser default, or a visual effect applied elsewhere.

What the CSSOM Represents

The Document Object Model (DOM) represents a document through nodes, elements, attributes, and their relationships. The CSSOM provides interfaces for styling information, including stylesheets, their rules, and declarations within those rules.

For example, consider this stylesheet rule:

.notice {
  color: navy;
  padding: 1rem;
}

The browser parses the selector and declarations. Through CSSOM interfaces, JavaScript can work with the stylesheet, inspect its rules where access is permitted, and read or change declaration values.

That rule alone does not describe the final presentation of every notice. A matching element may also receive styles from other rules, inline declarations, inherited values, browser defaults, animations, or transitions.

Two different questions therefore matter:

  • What CSS was declared? Stylesheet and declaration interfaces expose styling instructions.
  • What value applies to this element? Resolved-style inspection reflects the browser’s evaluation of those instructions for a particular element.

The CSSOM is not a second version of the document tree containing every element’s final appearance. It is also not the rendered page itself. Style calculation feeds into layout and the broader browser rendering pipeline, where boxes, positions, painting, and visual updates are determined.

How the Cascade and Inheritance Select Values

Before calculating dimensions or drawing content, the browser needs a value for each relevant CSS property on an element. The cascade resolves competing declarations. Inheritance and defaulting supply values where needed.

Which declarations are candidates?

A declaration must first be valid and applicable. Its selector must match the element, and any enclosing conditions—such as a media query—must be satisfied.

A rule inside a narrow-screen media query does not compete while that query is false. An unsupported property or invalid declaration may be discarded during parsing. Other failures, including some involving custom properties, can occur later during value resolution.

The cascade is more than specificity

Specificity helps resolve conflicts, but it is not the first or only consideration. The cascade also accounts for:

  • Origin: whether a style comes from the browser, the user, or the page author.
  • Importance: whether a declaration uses !important.
  • Cascade layers: explicitly ordered groups of author or other styles.
  • Element-attached styles: declarations in an element’s style attribute have special precedence within their origin and importance level.
  • Specificity: the relative weight of matching selectors after earlier precedence rules are considered.
  • Scoping proximity: for scoped rules, proximity can resolve otherwise tied declarations.
  • Order of appearance: later declarations can win when earlier factors remain tied.

Animations and transitions also participate in the cascade with their own precedence. Consequently, “the last rule wins” is reliable only after the other relevant factors have been accounted for.

Cascade layers make this especially visible: for normal author declarations, an unlayered rule can outrank a layered rule even if the layered selector is more specific. Important declarations reverse layer precedence, so adding !important is not simply a stronger version of ordinary source order.

Inheritance supplies values, not stronger selectors

Some properties, such as color and font-family, normally inherit from the parent. Others, such as margin, do not.

.notice {
  color: navy;
}

.notice a {
  color: darkred;
}

The link uses its own applicable color declaration rather than inheriting navy from the notice. A declaration on the parent does not compete against a declaration on the child as though they were two selectors targeting the same element.

For an inherited property without a declaration that supplies another value, the browser generally uses the parent’s computed value. For a non-inherited property without an applicable declaration, it generally uses the property’s initial value. Keywords such as inherit, initial, unset, and revert can alter that outcome.

Browser defaults are not the same as initial values

Browsers provide user-agent stylesheets that give familiar HTML elements their default presentation. These commonly make headings bold and give paragraphs margins.

A property’s initial value, however, comes from its CSS definition. For example, display has an initial value of inline, while browser styles ordinarily make a div behave as a block.

This distinction explains why display: initial does not mean “restore this element’s normal browser styling.”

Specified, Computed, Used, and Actual Values

CSS values pass through several conceptual stages. These stages explain how an authored expression becomes a value the browser can use for layout and presentation.

Major stages in CSS value resolution
Stage Meaning
Declared values Applicable declarations that are candidates for a property on an element.
Cascaded value The value selected by the cascade, if a declaration wins.
Specified value The value established after the cascade and defaulting, including inheritance or an initial value where required.
Computed value The specified value resolved as far as the property’s definition requires at this stage, often without needing layout.
Used value The value determined after remaining dependencies, such as layout dimensions, have been resolved.
Actual value The used value after any adjustments needed to represent it with the browser’s available capabilities.

A practical example

.container {
  width: 600px;
}

.card {
  box-sizing: content-box;
  font-size: 20px;
  padding: 1em;
  width: 50%;
}

Assume the card is a normal block inside the container, with no competing rules or additional sizing constraints.

  • padding: 1em resolves against the card’s font size, producing a computed padding of 20px on each side.
  • width: 50% retains its percentage at the computed-value stage.
  • Once the containing block’s width is known, the card’s used content width is 300px.
  • Because the card uses content-box, its horizontal padding adds another 40px to its outer width, before any borders or margins.

Not all percentages behave like percentage widths. For example, percentage font sizes can resolve during computed-value calculation. The stage at which a value becomes concrete depends on the property.

CSS pixels are also not necessarily physical screen pixels. Zoom, device pixel density, and rendering behavior affect how CSS dimensions are represented on a display.

Reading Styles with JavaScript

JavaScript offers several ways to inspect styling information. They answer different questions.

element.style reads inline declarations

<div class="notice" style="color: navy;">An update</div>
const notice = document.querySelector(".notice");

console.log(notice.style.color);
// "navy"

console.log(notice.style.padding);
// "" if padding has no inline declaration

An empty string does not establish that the element has no padding. Padding may come from a stylesheet. The style property exposes inline declarations, not the complete result of the cascade.

getComputedStyle() returns resolved values

const styles = window.getComputedStyle(notice);

console.log(styles.color);
console.log(styles.paddingTop);
console.log(styles.width);

Despite its name, getComputedStyle() returns resolved values. Depending on the property and the element’s state, these can correspond to computed values or used values.

For a laid-out element, a percentage width may therefore be reported as a pixel value. Colors and other values may also be serialized differently from their authored form. This method is useful for inspecting the result, but it is not a way to recover the original CSS text.

It also does not identify which selector or stylesheet supplied the winning declaration. Browser developer tools are usually more useful for tracing that origin.

Some pseudo-element styles can be inspected by passing a second argument:

const beforeStyles = window.getComputedStyle(notice, "::before");
console.log(beforeStyles.content);

Stylesheet inspection has access limits

document.styleSheets provides access to document stylesheets, but scripts cannot necessarily read every stylesheet’s cssRules. A cross-origin stylesheet may apply successfully while security restrictions prevent page JavaScript from inspecting its rules.

Browsers also restrict information that could expose browsing history through :visited styling. Style inspection is therefore not an unrestricted view of every internal browser decision.

Inline Styles and Dynamic Changes

Styles are not resolved only once during page load. Changing a class, adding a stylesheet, editing a rule, resizing the viewport, or altering an inline declaration can change which styles apply.

notice.classList.add("is-expanded");
notice.style.setProperty("--notice-accent", "darkgreen");

The first statement changes selector matching. The second adds or updates an inline custom property. Neither statement directly instructs the browser to repaint every part of the page; the browser determines which style, layout, and rendering work is necessary.

Inline styles are not absolute overrides. A normal inline declaration can lose to an applicable important declaration, and transitions introduce additional precedence considerations.

For maintainability, classes often work well for reusable interface states, while inline styles or custom properties can be appropriate for values calculated at runtime. The useful distinction is who owns the styling and how future editors can understand it—not a blanket prohibition on either technique.

Repeatedly writing styles and immediately reading layout-dependent values can also force synchronous browser work. When investigating performance, look for unnecessary alternation between DOM or style changes and measurements, rather than assuming every style read has the same cost.

A Practical Developer-Tools Workflow

A focused inspection should connect the visible symptom to the declaration, value, or layout condition responsible for it.

  1. Select the exact element. Check whether the visible feature belongs to the element, a child, or a pseudo-element. Confirm that you are inspecting the expected document or component.
  2. Review matching rules. In the Styles or Rules panel, find declarations for the property in question. Look for overridden values, invalid declarations, inherited styles, and inactive conditions.
  3. Inspect the resolved value. Use the Computed panel to see the resulting property value. Where the tool supports it, expand the property to trace the declarations contributing to it.
  4. Check layout dependencies. Inspect the box model, containing block, available space, and relevant flex or grid behavior. A correct declaration can produce an unexpected result because its surrounding layout differs from your assumption.
  5. Test one bounded change. Disable a declaration or temporarily alter a value. Check whether the symptom changes for the reason you expected.
  6. Correct the source and verify again. Apply the change to the stylesheet, component, theme, or script that owns it. Reload and test relevant viewport sizes and interaction states.

In WordPress, the source may be a theme stylesheet, block style, plugin, generated CSS, custom CSS setting, or inline output. A developer-tools edit is generally temporary; it does not by itself update that source.

This is part of a broader browser debugging workflow: observe the symptom, test an explanation, make a proportionate correction, and verify the result.

Why the Visible Result May Differ from Expectations

A declaration can be valid and active without producing the appearance someone anticipated. Common causes occur at different stages.

The declaration did not win

Another origin, importance level, cascade layer, inline declaration, or selector may take precedence. Increasing specificity will not solve every cascade conflict.

The value depends on surrounding layout

Percentage dimensions depend on a reference size. Flex items can shrink, minimum sizes can constrain a box, and grid track sizing can respond to content. Reading width alone may not explain the layout.

The property does not affect that kind of box as expected

For example, width does not size an ordinary non-replaced inline element in the same way it sizes a block. A value can appear in style inspection without governing the visible dimension in the expected way.

The measured box is not the painted shape

Padding and borders affect box dimensions. Transforms can change visual size or position without changing the space reserved by normal layout. Shadows and outlines can extend beyond the box without increasing its layout width.

An ancestor changes the visual outcome

A child may have opacity: 1 while appearing translucent because an ancestor has reduced opacity. Ancestor clipping, transforms, and stacking contexts can likewise affect the visible result without appearing as equivalent values on the child.

A font or runtime state changed

The computed font-family lists requested families; it does not establish that every character is rendered with the first font listed. Fallback fonts can change text dimensions. Hover, focus, animation, and JavaScript-driven states can also change presentation while inspection is underway.

The durable debugging habit is to keep three questions separate: What was declared? What value did the browser resolve? How did layout and rendering turn that value into the visible result? The CSSOM helps expose the styling information, but understanding the page requires following its relationship with the document, the cascade, and the surrounding layout.