Part 3 · 2 chapters · ~12 min

Style

From stylesheets to computed values: the CSSOM, the cascade, specificity and layers, matching and invalidation, scoping, then tokens, organisation strategies and loading.

8

The cascade and matching

How the browser decides one value per property per element, and what it costs to recompute after a change.

CSSOM

stylesheetrule: .cardpaddingcolorrule: @mediarule: .…rules, selectors, declarations

The CSS Object Model: the parsed stylesheets as a tree of rules the engine matches against the DOM and scripts can read.

in practicedocument.styleSheets, CSSStyleSheet, constructable stylesheets; why render waits for CSS.

deep dive: Browser P3

Cascade

origin + !importantuser…cascade layer@lay…specificity(id,…order of appearancelast…

The algorithm that picks one winning value per property per element from all matching declarations: origin and importance, layer, specificity, then order.

in practiceWhy a later rule loses to a more specific one, and why !important in a library is hostile.

deep dive: Browser P3

Specificity

selector(id, class, type)#nav a(1, 0, 1).nav .link:hover(0, 3, 0)nav a(0, 0, 2):where(.nav) a(0, 0, 1)

A weight per selector counted as (ids, classes and attributes and pseudo-classes, types and pseudo-elements), compared column by column.

in practiceThe specificity tooltip on a selector in the Styles pane; :where() to drop it to zero, :is() to take its argument's.

deep dive: Browser P3

Cascade layers

appwinscomponents (ds)resetlosesunlayered stylesbeat…

@layer declares named layers whose order beats specificity, so a reset or a design system can be overridden by app code without specificity wars.

in practice@layer reset, ds, app; a third-party stylesheet imported into a low layer.

deep dive: Browser P3 · Design P1

Inheritance

body: color #222p (inherits #…divspan (i…color inherits; margin does not

Some properties (color, font, line-height, visibility) pass from parent to child when the child has no value; most layout properties do not.

in practiceWhy setting font on body styles everything; inherit, initial, unset and revert keywords.

deep dive: Browser P3

Computed value

declaredfont-size: …cascadedwinnercomputed32pxusedafter layout

The value after the cascade and inheritance, with relative units resolved where possible; what getComputedStyle mostly returns.

in practiceThe Computed pane in DevTools; 2em turns into 32px there.

deep dive: Browser P3

Selector matching

spankey: match?.b ancestor?walk up.a ancestor?walk upright to left: the key selector filters first

Engines match selectors right to left: start from the key (rightmost) selector and walk up the ancestors only for elements that match it.

in practiceWhy ".a .b span" checks every span; why deep descendant selectors cost on big DOMs.

Bloom filter (style)

010110010010definitely not present, or maybe present

A compact probabilistic set of ancestor ids, classes and tags that lets the engine reject descendant selectors quickly without walking the tree.

in practicePart of why selector matching is fast enough in practice.

deep dive: Algorithms P2

Style invalidation

class changeon .listinvalidation….list .itemrestyle400 elements

Working out which elements need restyling after a DOM or class change, using invalidation sets so only affected elements are recomputed.

in practiceThe "Recalculate Style" slice and its "elements affected" count in the Performance panel.

deep dive: Browser P3 · DevTools P5

Recalculate Style

scriptRecalculate Sty…LayoutPaint117 ms

The trace event for the style stage: matching and computing styles for invalidated elements.

in practiceLong purple bars in the Performance panel after a class toggle on a high ancestor.

deep dive: DevTools P5

Shadow DOM scoping

page stylesstop at the boundaryexcept inherited propsand custom propsshadow stylesapply inside only:host for the host::part to expose

Styles inside a shadow root apply only within it, and outside styles do not reach in except through inherited properties, custom properties and ::part.

in practiceWeb components and design systems that must survive any host page's CSS.

deep dive: Browser P3

Container queries

in a sidebar (280px)stacked layoutin main (720px)side-by-side layoutsame component, container-sized

Styling an element by the size of its container rather than the viewport, so a component adapts wherever it is placed.

in practice@container (min-width: 400px) in a card used in both a sidebar and a main column.

deep dive: Browser P3 · Design P2
THE STYLE LAYER, IN ORDER
from stylesheets to a computed value on every element, and the ways teams organise CSS
swipe the figure sideways, or tap expand for full screen
1/6
parse
Parse: stylesheets become the CSSOM; custom properties and registered @property values enter it like any other declaration.
9

Variables, organisation and loading

How teams store decisions, keep CSS from colliding, and get styles on screen without a flash.

Custom properties

:root { --brand: #4338CA; }.dark { --brand: #A5B4FC; }.btn { background: var(--brand); }resolved per element, at runtime

CSS variables (--name) that cascade and inherit like any property and are resolved at computed-value time, so they can change per element and at runtime.

in practiceDesign tokens, themes, dark mode, per-component overrides; the source of this site's module colours.

deep dive: Browser P3 · Design P1

@property

@property --angle { syntax: '<angle>'; inherits: false; initial-value: 0deg;}.spin { transition: --angle 1s; }

Registering a custom property with a type, initial value and inheritance, so it can be animated and validated.

in practiceAnimating a gradient angle or a numeric token smoothly.

deep dive: Browser P3

Design tokens

componentbutt…semanticacti…primitiveblue…

Named design decisions (colours, spacing, type, radii, motion) stored once and compiled to CSS variables, platform constants and docs.

in practiceA tokens.json that becomes CSS, iOS and Android values; theming without forking components.

deep dive: Design P1

BEM

.cardblock.card__titleelement.card--featuredmodifier

Block, Element, Modifier: a naming convention (.card__title--large) that keeps specificity flat and makes ownership obvious.

in practiceOlder large codebases; still a sane default without CSS modules.

deep dive: Design P2

CSS Modules

.titlein Card.module.c…buildhash.Card_title_…unique

Class names scoped per file at build time by renaming them to unique hashes, so styles cannot collide across components.

in practiceimport styles from "./Card.module.css"; class names like Card_title__x7a2.

deep dive: Architecture P1

Utility-first CSS

<button class="px-4 py-2 rounded-md bg-indigo-600 text-white hover:bg-indigo-700"> Pay</button>

Composing small single-purpose classes in markup (Tailwind) instead of writing component stylesheets; the CSS stops growing with features.

in practiceTailwind class strings; a constant-size stylesheet across a growing app.

deep dive: Design P2

CSS-in-JS

runtime CSS-in-JSstyles built on renderinject <style>costs in INPSSR frictionzero-runtimeextracted at buildstatic .cssno render costtyped tokens

Writing styles in JavaScript and generating CSS at runtime (styled-components, Emotion) or at build time (vanilla-extract, Linaria, StyleX).

in practiceRuntime libraries cost style recalc on render and conflict with streaming SSR; the industry moved to zero-runtime.

Critical CSS

html + inline c…first paintfull css (async)200 ms

The minimum CSS for above-the-fold content, inlined in the HTML so first paint does not wait for the full stylesheet.

in practiceA style tag in head plus a deferred full stylesheet; LCP improvements on slow networks.

deep dive: Browser P3

Flash of unstyled content

HTML paintedunstyledCSS arrivesrestyle + ju…CLS

Content painted before its styles arrive, then jumping when they do.

in practiceStylesheets loaded asynchronously without critical CSS; client-only CSS-in-JS on SSR pages.

deep dive: Browser P3

Font loading (font-display)

blockinvisible up to 3sswapfallback now, swap laterfallbackshort block, short swapoptionaluse only if cached or fast

How text behaves while a web font loads: block (invisible), swap (fallback first), optional (use only if fast).

in practiceInvisible text on slow networks (FOIT); layout shift when the font swaps (FOUT); size-adjust to match fallback metrics.

Logical properties

physicalmargin-leftpadding-rightleft: 0breaks in RTLlogicalmargin-inline-startpadding-inline-endinset-inline-start: 0mirrors with dir

Direction-relative properties (margin-inline-start, padding-block) that flip automatically for right-to-left and vertical writing modes.

in practiceRTL support for Arabic and Hebrew without an override stylesheet.

deep dive: Disciplines P7

:has()

/* style the card only if it has an image */.card:has(> img) { grid-template-rows: 160px auto; }form:has(:invalid) .submit { opacity: .5; }

A relational pseudo-class that selects an element by what it contains, the long-wanted "parent selector".

in practiceform:has(:invalid), .card:has(img); invalidation cost if used on very high ancestors.

deep dive: Browser P3