Observation Guide

DevTools for style and layout

WhereWhat it tells you
Performance → Recalculate Style blockshow long selector matching + value computation took, and how many elements
Performance → Layout blocksgeometry cost; the "Nodes That Need Layout" line is the scope
Performance → red triangleforced synchronous layout, with the causing stack (fe-01)
Rendering → Paint flashingwhat actually repainted; large flashes on small changes mean bad containment
Rendering → Layer borderscompositor layers; a sea of borders means will-change overuse
Rendering → Layout Shift RegionsCLS contributors
Elements → Computed → the funnel iconwhich rule won, and what it beat
Elements → Styles → strikethroughoverridden declarations — the cascade, visualised

"Nodes That Need Layout: 12 of 40,000" in a Layout block is the single most useful containment diagnostic. A high ratio means one change is invalidating the world, which is exactly what contain: layout + contain: size bounds.

Reading a containment decision

Before adding content-visibility: auto, check:

  1. Is most of it offscreen? If everything is visible there is nothing to skip.
  2. Can you estimate a size? Without contain-intrinsic-size the scrollbar will be wrong and scroll position will jump.
  3. Does anything inside need to be measured while offscreen? Sticky headers, IntersectionObserver targets and scroll-spy will behave differently.
  4. Does find-in-page still work? Test it. Skipped content remains searchable in modern Chromium — that is precisely why this beats display: none — but verify rather than assume.

Before adding contain: size, check that the box genuinely has an author-determined size. Content that must grow inside a size-contained box is clipped or collapses to zero. The optimisation and the correctness constraint are the same declaration.

Healthy vs unhealthy

Healthy:

  • Layout blocks show a small "nodes that need layout" relative to total
  • Paint flashing highlights only what changed
  • Layer borders appear on a handful of elements, not hundreds
  • !important count is flat or falling over time
  • Long lists carry content-visibility or are virtualized deliberately

Unhealthy:

  • Recalculate Style blocks over ~10 ms on interaction — usually a :root custom-property change invalidating everything, or a very expensive selector
  • Layout blocks touching most of the document for a local change — missing containment
  • will-change in a base component class rather than applied around an animation
  • Rising !important count — specificity ratchet; the structural fix is layers
  • z-index escalation — usually an unrecognised stacking context

What these measurements do not tell you

  • Headless is not headed for compositing decisions. These experiments measure style and layout; do not extend the numbers to paint or GPU claims.
  • Viewport-dependent. Every content-visibility result is a function of how much is offscreen.
  • One engine. Containment support and invalidation heuristics differ across engines; Safari and Firefox will not reproduce these ratios.
  • Synthetic content. Real cards contain images, custom fonts and nested components with their own invalidation behaviour.
  • No paint or raster cost measured. contain: paint benefits are not captured by RecalcStyleDuration or LayoutDuration at all.