CSS Nesting
Prerequisites: Cascade layers
Native CSS now supports nesting — rules inside rules, as in Sass — in all major browsers (Chrome and Edge 120, Firefox 117, Safari 17.2 for the current relaxed syntax).
.card {
padding: 1rem;
border: 1px solid #ddd;
& h2 {
margin-top: 0;
}
p {
color: #444;
}
&:hover {
border-color: #0b57d0;
}
&.featured {
border-width: 2px;
}
.dark-theme & {
border-color: #444;
}
@media (width >= 40rem) {
padding: 2rem;
}
}
&stands for the parent selector.&:hoveris.card:hover;&.featuredis.card.featured;.dark-theme &is.dark-theme .card.- A nested selector without
&(likep) is treated as a descendant:.card p. - Media queries and other conditional rules can nest directly and apply to the parent selector.
How nested selectors are matched
The browser treats a nested rule like :is(parent) nested. That has two
consequences Sass users should know:
<style>
#hero, .banner {
& p { color: rgb(255, 0, 0); }
}
.banner p { color: rgb(0, 0, 255); }
</style>
<div class="banner"><p>Text</p></div>
<script>
const rule = document.styleSheets[0].cssRules[0].cssRules[0];
console.log(rule.selectorText);
console.log(getComputedStyle(document.querySelector(".banner p")).color);
</script>
& p
rgb(255, 0, 0)
- The CSSOM keeps the nested selector as written (
& p), but it matches like:is(#hero, .banner) p, so its specificity is 1,0,1 even for the.bannermatch — it beat the later.banner p. - You can't concatenate strings:
&__titledoes not produce.card__titleas in Sass. BEM-style suffixes still need a preprocessor or full class names.
Guidelines
- Nest one or two levels at most. Deep nesting produces long, specific selectors — the same problems as writing them by hand.
- Nest states (
&:hover,&:focus-visible), modifiers, and media queries; avoid nesting just to mirror the HTML structure. - Use a build tool (Lightning CSS, PostCSS) to flatten nesting only if you must support browsers from before late 2023.
Practice
Rewrite this with nesting:
.nav { display: flex; }
.nav a { color: white; }
.nav a:hover { text-decoration: underline; }
@media (width < 40rem) { .nav { flex-direction: column; } }
Show solution
.nav {
display: flex;
a {
color: white;
&:hover {
text-decoration: underline;
}
}
@media (width < 40rem) {
flex-direction: column;
}
}