CSS Nesting
Native CSS nesting lets you write child selectors directly inside a parent rule block, instead of repeating the parent selector over and over across separate flat rules. It ships as part of the CSS engine itself — no Sass, Less, or PostCSS plugin required — and browser support has matured quickly across evergreen browsers since it landed.
Before and after: the problem nesting solves
Without nesting, styling a component and its internal parts means repeating the component's selector for every related rule. It works, but it scatters the "shape" of a component across many separate blocks and makes the relationship between them harder to see at a glance.
/* Flat selectors — repetitive, and the relationship between
.card, .card-title and .card:hover is only implicit */
.card {
padding: 1rem;
border: 1px solid #ddd;
border-radius: 8px;
}
.card-title {
font-size: 1.25rem;
font-weight: 600;
}
.card:hover {
border-color: #0066cc;
}
.card:hover .card-title {
color: #0066cc;
}With native nesting, the same styles can be written as one block, where indentation visually communicates the structure:
.card {
padding: 1rem;
border: 1px solid #ddd;
border-radius: 8px;
/* Nested descendant selector — no & needed, this behaves
like ".card .card-title" */
.card-title {
font-size: 1.25rem;
font-weight: 600;
}
/* Nested compound selector attached to the parent itself —
the & IS required here, see below */
&:hover {
border-color: #0066cc;
.card-title {
color: #0066cc;
}
}
}The & nesting selector
& symbol represents the parent selector at the point it's used, and is what lets you combine a nested rule with the parent instead of treating it as a descendant. This matters a lot for pseudo-classes, pseudo-elements, and anything that needs to attach directly to the parent rather than select something inside it..button {
background: #0066cc;
color: white;
/* &:hover means ".button:hover" — attached directly */
&:hover {
background: #0052a3;
}
/* &.is-disabled means ".button.is-disabled" */
&.is-disabled {
opacity: 0.5;
pointer-events: none;
}
/* & followed by a space is a descendant combinator: ".button .icon" */
& .icon {
margin-right: 0.5em;
}
/* &::before means ".button::before" */
&::before {
content: '';
}
}.card-title or img) is automatically treated as a descendant of the parent — no & needed. But a selector that starts with a pseudo-class, pseudo-element, or combinator token (like :hover, ::before, or > li) needs the parent reference spelled out, which is exactly what & provides. In practice, many developers write & explicitly everywhere for consistency, even where it's optional.Nesting media queries and at-rules
@media and @supports can be nested inside a selector block too, which keeps responsive tweaks for a component next to the component's base styles instead of in a separate media-query section far away in the file..sidebar {
width: 100%;
@media (min-width: 768px) {
width: 260px;
}
@supports (container-type: inline-size) {
container-type: inline-size;
}
}Combining multiple selectors
.nav-link {
color: #333;
/* Comma-separated nested selectors both attach via & */
&:hover,
&:focus-visible {
color: #0066cc;
text-decoration: underline;
}
}&:hover. If you find yourself nesting past three levels, it's usually a sign the inner selector deserves its own class and its own top-level rule instead.Native nesting vs. Sass nesting
& requirement — Sass lets you omit
&in more places and infers the relationship more loosely; native CSS nesting is stricter about requiring&for anything that isn't a plain type/class/ID/attribute selector.Compile step — Sass nesting is compiled away before the browser ever sees it, so there was never a browser-support question. Native nesting is parsed and evaluated by the browser itself at runtime.
Interpolation — Sass supports building selector names dynamically (
&-#{$modifier}); native CSS nesting has no equivalent, since it has no variables/logic layer of its own beyond custom properties.Error handling — an invalid nested rule in Sass fails at build time with a clear compiler error; an invalid native nested rule is simply ignored by the browser at runtime, which can be harder to notice.
@property lets a custom property carry a real type: [@property — typed custom properties](/css/at-property).