Key takeaways
- Container queries change component architecture more than media queries ever did — components can finally be genuinely context-independent.
:has()gives CSS a parent selector, removing a large class of JavaScript that existed purely to add state classes.- Cascade layers make specificity a design decision rather than an accident of source order.
- Native nesting and
@scoperemove much of the remaining case for CSS-in-JS.
CSS has absorbed more genuinely useful capability in the past two years than in the decade before it, and the effect on design systems is structural rather than cosmetic. Several patterns that required JavaScript, build tooling, or elaborate naming conventions are now single-line platform features.
Container queries
Media queries ask about the viewport. That was always the wrong question for a component, because a card does not care how wide the window is — it cares how much space it has been given.
The practical consequence of that mismatch was that components could not be truly reusable. A card in a sidebar and the same card in a main column needed different breakpoints, so design systems accumulated variant props, wrapper classes, and context-specific overrides to compensate.
Container queries let a component respond to its own container's dimensions. The same component, dropped into a narrow sidebar or a wide grid cell, adapts correctly with no external configuration. This removes an entire category of variant proliferation from design systems.
Container query units — cqw, cqi, and their relatives — extend the same idea to typography and spacing, so a component can scale proportionally to its allocated space rather than to the page.
The :has() selector
CSS has never had a parent selector, and the workarounds were pervasive: JavaScript that watched for a state change and toggled a class on an ancestor purely so CSS could style it.
:has() eliminates most of that. A form field wrapper can style itself when it contains an invalid input. A card can change layout when it contains an image. A navigation item can highlight when it contains the active link. All declaratively, with no state synchronisation to get wrong.
The second-order effect is more interesting than the selector itself: less JavaScript observing the DOM to inform CSS means fewer opportunities for the two to disagree about the current state.
Cascade layers
Specificity conflicts have historically been resolved by escalation — add another class, add an ID, eventually add !important. The result in mature design systems is a specificity ratchet that only goes up.
Cascade layers let you declare precedence explicitly. Define layers for resets, base styles, component styles, utilities, and overrides, and their order determines which wins regardless of selector specificity within them. A single-class utility in a later layer beats a deeply nested component selector in an earlier one.
This turns specificity from something you fight into something you architect, and it makes third-party CSS far easier to integrate — drop it in its own layer and its internal specificity choices stop mattering.
Nesting and scope
Native CSS nesting removes the most common reason teams reached for a preprocessor. Combined with @scope, which limits a rule's reach to a subtree with an optional lower boundary, much of the encapsulation that CSS-in-JS provided is now available without a runtime or a build step.
This does not make CSS-in-JS obsolete — dynamic styling from application state remains a genuine use case — but it substantially narrows the set of problems it is the right answer to.
Colour and typography
Wide-gamut colour spaces, oklch() in particular, matter more for design systems than for individual pages. Because oklch is perceptually uniform, a programmatically generated palette produces steps that look evenly spaced, which they emphatically do not in HSL. Generating an accessible colour ramp from a single brand colour becomes a calculation rather than a manual tuning exercise.
Fluid typography with clamp() has largely replaced breakpoint-stepped type scales, and doing the same with container query units makes type scale with its component rather than the viewport.
What this means in practice
- Fewer component variants. Context-responsiveness replaces explicit size props.
- Less JavaScript in the styling path. State that CSS can observe directly no longer needs a synchronisation layer.
- Specificity as architecture. Layers make override behaviour intentional and documented.
- Smaller dependency footprint. Preprocessors and runtime styling libraries become optional rather than assumed.
The migration is worth doing incrementally. Cascade layers can be introduced without touching existing selectors, and container queries can be adopted component by component. Neither requires a rewrite, which is precisely why they are worth starting on now.
Comments (2)
Alex Thompson
65w ago
Incredible analysis. The points about multimodal reasoning are spot on — this is exactly the kind of deep dive we need to understand these models properly.
Nour Al-Rashid
65w ago
Great article! I appreciate the balanced approach — acknowledging both the capabilities and the safety considerations. Looking forward to your follow-up piece.