The cascade breaks first
Style collision is the first failure of every graft and the last one anybody budgets for — cascade layers and containment each solve half of it, and neither solves inheritance, which is the half that actually bites.
The seam map says the review gate can go in through the shared layout template. You add the script tag, reload, and the component renders. It also renders wrong: the confidence badges are the host’s link blue, the approve button has picked up a 44-pixel line height from a bare button rule written in 2018, and one of your labels is uppercase for reasons nobody can locate.
Nothing threw. No test failed. This is what the first failure of a graft always looks like, and it is worth being precise about why it is first: it is the only failure mode that fires on contact. Auth, bundling, accessibility and events all fail later, under conditions somebody has to construct. Style collision happens the instant your markup enters their document, in every host, with no configuration required.
Two directions, and only one of them gets budgeted
A style collision has a direction, and the two directions have very different consequences.
- Their styles reaching into you. Your component renders wrong. Embarrassing, immediately visible, and yours to fix.
- Your styles leaking out into them. Their application renders wrong, on a page you have never seen, reported by a customer. This is the one that gets a graft removed.
Almost every technique below is asymmetric — it defends one direction well and the other badly or not at all. Knowing which is which is most of the skill.
Cascade layers are not a defence. They are a liability.
The reflex, given @layer, is to be a good citizen: wrap your component’s CSS in a low-priority layer so it cannot bully the host. That reflex produces a component that loses every fight.
MDN states the rule without hedging: “Normal styles in a layer take precedence over styles declared in prior layers; with normal styles declared outside of any layer taking precedence over normal layered styles regardless of specificity.” The @layer reference puts it even more bluntly: “Styles that are not defined in a layer always override styles declared in named and anonymous layers.”
Read that against a real host. A 2018 global stylesheet has no layers in it at all — every rule in it is unlayered. If you put your component styles in a layer, a bare button { line-height: 44px} in their unlayered reset beats your .agent-review-gate__approve rule outright. Not on specificity — specificity is not consulted. Layered loses to unlayered before specificity is reached.
And !important does not rescue you, because it inverts both halves of the ordering, not just one. MDN: “Important styles declared outside of any cascade layer have lower precedence than those declared as part of a layer. Important styles that come in early layers take precedence over important styles declared in subsequent cascade layers.” So for normal declarations the order runs first layer, then later layers, then unlayered on top; for !important it runs exactly backwards — unlayered lowest, then late layers, with the earliest layer winning.
Containment does not contain styles
The next reflex is contain, on the reasonable assumption that a property with style as one of its values isolates styles. It does not, and MDN says so directly: “Despite the name, style containment does not provide scoped styles such as you would get with the Shadow DOM or @scope. The main use case for the style value is to prevent situations where a CSS counter could be changed in an element, which could then affect the rest of the tree.”
Containment is a performance and layout mechanism, and it is genuinely useful to a graft — just not for the reason you reached for it. contain: layout gives you an independent formatting context, so your internal layout cannot be disturbed by the host’s and cannot disturb theirs; margins stop collapsing across the boundary. contain: paint clips your descendants to your bounds, which stops a stray absolutely-positioned child of yours from landing in the middle of their navigation.
Both of those are real graft problems that layers do not solve. Neither of them is style isolation. Reach for containment to stop geometry leaking in both directions, and do not let the value name convince you it did anything about the cascade.
The half nothing solves: inheritance
Here is the part that survives every mechanism in this course, and the reason the cascade lesson comes before the mechanism-choice lesson rather than after it.
Shadow DOM gives you the real thing. MDN: “The page CSS does not affect nodes inside the shadow DOM”, and equally “none of the code inside a shadow DOM can affect anything outside it.” Both directions, by construction, with no naming discipline required.
But selector matching is not the only way a style arrives. Inheritance is computed over the flattened tree, which includes the shadow tree — the CSS Scoping specification notes that slotted content “inherit[s] from the slot they’re assigned to,” not from their light-DOM parent, precisely because inheritance runs on the flat tree rather than the original one (CSS Scoping Module Level 1). So every inherited property still crosses: color, font-family, font-size, line-height, letter-spacing, visibility, and every CSS custom property.
That leak is not a bug and you do not want it closed. It is the entire mechanism by which a grafted component can look like it belongs in ninety different products. It is also why “encapsulated” components still render wrong: a host that sets line-height: 1.9 on body has just set it inside your shadow root, and no amount of encapsulation stopped it.
The blunt instrument is all: initial, which resets every property to its specification-initial value rather than its inherited one — unlike unset and revert, both of which re-inherit for inherited properties. But read what all actually covers: MDN says it “resets all of an element’s properties except unicode-bidi, direction, and CSS Custom Properties.” Custom properties are explicitly exempt. A host’s --brand-primary passes straight through your reset boundary, which is fortunate for theming and startling the first time a host variable collides with one of yours by name.
Where people get burned
This is where a name collision becomes a production incident. If the host defines --surface and you consume --surface, you have silently joined their theme — and there is no isolation boundary anywhere in this course that stops it, because custom properties inherit and no reset removes them. Namespace every custom property you read or write with a prefix that could not plausibly be theirs. This is the cheapest defence in the whole lesson and the one most often skipped.
Where @scope fits, and where it trips you
@scope is the newest option and it does something the others do not: it limits which elements a rule may match, with a lower bound as well as an upper one. MDN records it as Baseline 2026, newly available — working across the latest browsers since March 2026 (checked 2026-09-03). “Newly available” is a specific status: per Baseline, it means all four core browser engines support it now, and it becomes “widely available” only after thirty months. For a host with an older browser floor, that is not yet a mechanism you may rely on.
The trap is specificity. Bare selectors inside a scope behave as though :where(:scope) were prepended, contributing nothing — but writing :scope explicitly adds class-level specificity, because it is a pseudo-class:
@scope (.agent-review-gate) {
img { /* specificity 0-0-1 */ }
:scope img { /* specificity 0-1-1 — not the same rule */ }
}Two lines that read as equivalent and are not. In a host where you are already losing specificity fights, a one-character difference decides them.
Check your recall
Answer from memory — no scrolling back.
Retrieval check
Name the one style problem that Shadow DOM, cascade layers, containment and `all: initial` all fail to solve — and say why you would not want it solved.
Check your answer
Inheritance. Inherited properties are computed over the flattened tree, which includes shadow content, so color, font-family, line-height and every custom property cross every boundary on the list. Cascade layers govern declaration precedence, not inheritance. Containment scopes counters and quotes. And all: initial, the only one that severs inheritance at all, explicitly exempts custom properties.
You would not want it solved because that leak is how a graft inherits a host’s brand. A component that inherits type and colour looks native in ninety products without ninety builds; one that is fully sealed looks like a foreign object in all of them. The engineering job is not to close the leak — it is to be deliberate about which properties you let through and which you re-declare, and to namespace your custom properties so the inheritance you get is the inheritance you asked for.
Hands on
Measure the collision before you design around it
Done when: ARTIFACT.md carries a constraint sheet for one real host, listing its bare-element rules, its !important count, whether it uses @layer, and at least three named inherited properties your component will receive whether it wants them or not — each backed by a number you measured, not estimated.
- Take the host you mapped in the host-reading lesson. Pull its compiled global stylesheet — the shipped one, not the source, because the build may add a framework reset you did not see in the repo.
- Count three things: rules whose selector is a bare element name (
button,input,label,p), occurrences of!important, and occurrences of@layer. Write the three numbers down. The third one decides whether layers are available to you at all. - Now measure the inheritance you will receive. Open the host in a browser, select a deeply nested element, and record the computed values of
color,font-family,font-size,line-heightandletter-spacing. Those are the values your component starts with inside a shadow root — not your defaults, theirs. - List every CSS custom property defined on the host’s
:root. In the console:[...document.styleSheets].flatMap((s) => [...s.cssRules])and read the:rootrule, or just read the Computed panel with “Show all” on. Flag any name that collides with one you would naturally have chosen. - Write the sheet into
learning/grafting-ui/ARTIFACT.mdunder the constraint-sheet checkpoint, and add one line naming the prefix every custom property in your bundle will carry. Pick it now, before any code exists, because renaming it later means renaming it in every host. - Bring the sheet into the chat. I will push back on any inherited value you wrote down as a default rather than as a measurement.
What this does not cover
This lesson measured the collision and named the defences without picking one, on purpose. Style isolation is only one axis, and it is the one people over-weight — a mechanism that isolates perfectly and cannot inherit the host’s session, or cannot survive their Content Security Policy, is not a better answer than one that leaks a little. The delivery-constraints lesson puts all six mechanisms on one table and scores them on axes you do not get to choose.
What the shadow boundary costs once you are inside it — focus, form participation, event retargeting — belongs to the Web Components module, and the specific and still-unsolved problem of ARIA references across a shadow root gets its own lesson there, because the honest answer is worse than most component libraries admit.
Read this next — primary source
Introducing the CSS cascadeMDN Web Docs — free
This lesson takes exactly one rule from it — that unlayered normal declarations beat every layered one, and that !important inverts the whole ordering. The full page has the rest of the algorithm the graft has to survive: origin precedence (user-agent, user, author), how transitions and animations sit above everything, and where scoping proximity now enters the ordering. Read it end to end once, because the ordering is not intuitive in three separate places and each one of them is somewhere a graft can lose a style fight it looks like it should win.
Stuck, curious, or think this lesson is wrong? Ask your teaching agent. The lessons are the scaffold; the conversation is where the learning gets unstuck.