Observation Guide
DevTools for style and layout
| Where | What it tells you |
|---|---|
| Performance → Recalculate Style blocks | how long selector matching + value computation took, and how many elements |
| Performance → Layout blocks | geometry cost; the "Nodes That Need Layout" line is the scope |
| Performance → red triangle | forced synchronous layout, with the causing stack (fe-01) |
| Rendering → Paint flashing | what actually repainted; large flashes on small changes mean bad containment |
| Rendering → Layer borders | compositor layers; a sea of borders means will-change overuse |
| Rendering → Layout Shift Regions | CLS contributors |
| Elements → Computed → the funnel icon | which rule won, and what it beat |
| Elements → Styles → strikethrough | overridden 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:
- Is most of it offscreen? If everything is visible there is nothing to skip.
- Can you estimate a size? Without
contain-intrinsic-sizethe scrollbar will be wrong and scroll position will jump. - Does anything inside need to be measured while offscreen? Sticky headers, IntersectionObserver targets and scroll-spy will behave differently.
- 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
!importantcount is flat or falling over time- Long lists carry
content-visibilityor are virtualized deliberately
Unhealthy:
- Recalculate Style blocks over ~10 ms on interaction — usually a
:rootcustom-property change invalidating everything, or a very expensive selector - Layout blocks touching most of the document for a local change — missing containment
will-changein a base component class rather than applied around an animation- Rising
!importantcount — 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-visibilityresult 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: paintbenefits are not captured byRecalcStyleDurationorLayoutDurationat all.