Concept
CSS has shipped an unusually large batch of genuinely new capabilities in the last few years, not syntax sugar, but features that close real, long-standing gaps: a working parent/relational selector, native nesting, explicit cascade ordering independent of source order, and subgrid. Several directly eliminate patterns that used to require JavaScript.
:has(), the "parent selector" CSS never had
/* Style a form group differently if it contains an invalid input, impossible before :has() without JS */
.form-group:has(input:invalid) {
border-color: red;
}
/* Style a card differently if it contains an image */
.card:has(img) {
grid-template-columns: 120px 1fr;
}
/* Style a label based on its sibling input's state */
label:has(+ input:focus) {
color: #4f46e5;
}For the entire history of CSS before :has(), there was no way to select an element based on its descendants or following siblings, only ancestors could style descendants, never the reverse. :has() is a genuine, long-requested capability gap closed: it lets a selector match based on whether it contains (or is followed by) something matching an inner selector. This directly replaces a real, common category of JS previously required purely for conditional styling (adding/removing a class via JS just to express "this container has an invalid child").
Native CSS nesting
.card {
padding: 1rem;
& .title {
font-size: 1.25rem;
}
&:hover {
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
@media (min-width: 768px) {
padding: 2rem; /* media queries can nest directly inside a rule now too */
}
}Covered from the Sass comparison angle in the SCSS topic, native nesting now closely mirrors what Sass offered, compiled by the browser itself with no build step. The & explicit-nesting selector works very similarly to Sass's &. One notable difference to verify for your target browser support: early native nesting specs required & for compound selectors in some cases where Sass was more permissive, check current spec/browser behavior rather than assuming 1:1 Sass parity for edge cases.
Cascade layers (@layer), explicit specificity ordering, independent of source order or selector specificity
@layer reset, base, components, utilities;
@layer reset {
* { margin: 0; padding: 0; }
}
@layer components {
.button { padding: 8px 16px; background: blue; }
}
@layer utilities {
.bg-red { background: red !important; } /* still wins over .button even without high selector specificity */
}This is a direct, native-CSS answer to exactly the problem ITCSS's file-ordering convention (covered in the CSS Architecture topic) manually enforces through discipline: @layer lets you declare explicit layer precedence, layers declared later always win over layers declared earlier, regardless of the actual selector specificity or source order within each layer. A single class selector in a later layer beats an ID selector in an earlier layer, which is never true under normal CSS cascade rules. This turns ITCSS's "organize files so specificity increases monotonically" convention into an actual browser-enforced guarantee rather than a discipline that has to be manually maintained and can be accidentally violated.
/* Even a "worse" (lower specificity) selector in a LATER layer still wins */
@layer components {
#special-button { background: blue; } /* ID selector, normally very high specificity */
}
@layer utilities {
.bg-red { background: red; } /* single class, but WINS because utilities is a later layer */
}