Nesting
|
This section documents Sass/SCSS as implemented by Dart Sass, the current official and actively maintained compiler — it is not tied to any specific book, build tool, or CSS framework (Bootstrap, Bulma, etc.). Unlike the other reference sections on this site, no single reference book underpins it: the content was generated with the assistance of AI from general knowledge of Dart Sass, and should be verified against the current official documentation at sass-lang.com before relying on it in production. Sass continues to evolve, so behaviour described here may lag the compiler you are actually running. This section’s bibliography lists the reference material consulted while preparing these pages. |
Nesting lets a stylesheet’s structure mirror the structure of the markup it styles, so a shared selector prefix is written once instead of repeated on every line. At compile time Sass flattens the nesting back into ordinary descendant selectors.
Nested selectors
nav {
background: #222;
ul {
margin: 0;
padding: 0;
list-style: none;
li {
display: inline-block;
a {
display: block;
padding: 6px 12px;
color: white;
}
}
}
}
Each nesting level becomes a descendant combinator (a space) in the output:
nav { background: #222; }
nav ul { margin: 0; padding: 0; list-style: none; }
nav ul li { display: inline-block; }
nav ul li a { display: block; padding: 6px 12px; color: white; }
The flattening is purely textual concatenation of the selector at each level:
Keep nesting shallow
Nesting is the feature most often overused. Every level adds a descendant combinator, and the result is long,
highly specific selectors that are hard to override later (see
Selectors & specificity for why specificity escalation hurts).
The example above produces nav ul li a — four levels of specificity to style a link that a single .nav-link
class would have handled.
A widely used rule of thumb is the inception rule: never nest more than three levels deep. In practice, combining shallow nesting with flat, meaningful class names (BEM, below) is better than deep nesting that mirrors the DOM exactly.
Other selectors can be nested too, not just descendants — put the combinator before the nested selector:
.menu {
> li { /* .menu > li -- direct children only */ }
+ .menu { /* .menu + .menu -- adjacent sibling */ }
~ .footer { /* .menu ~ .footer -- general sibling */ }
}
The & parent selector
& stands for the full compiled selector of the enclosing block. It is what makes nesting useful for anything
other than descendants, because it lets the nested selector attach directly to the parent rather than being
separated by a space.
.button {
background: #3f51b5;
color: white;
&:hover { background: #32408f; } // .button:hover
&:focus-visible { outline: 2px solid; } // .button:focus-visible
&::before { content: "→ "; } // .button::before
&.is-active { background: #1a237e; } // .button.is-active
&[disabled] { opacity: 0.5; } // .button[disabled]
}
Without &, writing :hover { … } inside .button would compile to .button :hover — a descendant — which is almost never what is meant.
& in other positions
& need not come first. Putting something before it inverts the relationship, which is the idiomatic way to
express "when some ancestor or state applies to me":
.icon {
fill: currentColor;
.button & { margin-right: 4px; } // .button .icon
[data-theme="dark"] & { fill: #ccc; } // [data-theme=dark] .icon
.sidebar &:hover { fill: red; } // .sidebar .icon:hover
}
Suffixing & for BEM
& can also be concatenated with a plain string suffix, which is what makes Sass a natural fit for BEM-style
naming — the block name is written once and each element/modifier hangs off it:
.card {
padding: 1rem;
border: 1px solid #ddd;
&__title { font-size: 1.25rem; } // .card__title
&__body { color: #555; } // .card__body
&--featured { // .card--featured
border-color: gold;
.card__title { color: gold; }
}
}
This is convenient, but it has a real cost: searching the codebase for .cardtitle will not find
&title. Teams that rely heavily on grep-ability often prefer to write such class names out in full.
Note that & always represents the entire parent selector, including every comma-separated part of it. When
the parent is a selector list, & expands to that whole list, which can produce more output than expected:
.alpha, .beta {
& + & { margin-top: 8px; }
}
// compiles to:
// .alpha + .alpha, .alpha + .beta, .beta + .alpha, .beta + .beta { margin-top: 8px; }
& is also available as an ordinary value inside @if, functions, and interpolation, which lets mixins branch
on the context they were included in.
Nested properties
CSS has many families of properties sharing a hyphenated prefix — font-, margin-, border-,
background-. Sass lets that prefix be factored out into a nested block:
.headline {
font: {
family: Georgia, serif;
weight: bold;
size: 2em;
style: italic;
}
margin: auto {
top: 2rem;
bottom: 1rem;
}
}
Compiles to:
.headline {
font-family: Georgia, serif;
font-weight: bold;
font-size: 2em;
font-style: italic;
margin: auto;
margin-top: 2rem;
margin-bottom: 1rem;
}
Note the colon after the prefix (font:), and the second form — margin: auto { … } — which sets the
shorthand and specific longhands in one block.
Nesting media queries
A @media rule may be nested inside a style rule, and Sass will bubble it up to the top level of the output
while preserving the selector. This lets a component’s responsive behaviour live next to its base styles rather
than in a separate breakpoint section at the bottom of the file:
$breakpoint-md: 768px;
.sidebar {
width: 100%;
@media (min-width: $breakpoint-md) {
width: 280px;
float: left;
}
}
.sidebar { width: 100%; }
@media (min-width: 768px) {
.sidebar { width: 280px; float: left; }
}
Nested media queries pair particularly well with a breakpoints map and a mixin — see Mixins and Lists & Maps.
Comparison with native CSS nesting
CSS itself now has nesting, and modern browsers support it directly with no build step. It was explicitly modelled on what Sass had been doing for years — see the Native CSS nesting subsection of Selectors & specificity for the CSS-side rules.
The syntax is close enough that most Sass nesting reads as valid CSS nesting, but a few differences are worth knowing:
-
&is not optional in the same places. Native CSS is stricter about where a bare element selector may appear nested, and&behaves as a real compound selector there rather than a textual substitution. -
String concatenation does not work.
&__titleis a Sass-only trick. Native CSS nesting cannot build a new class name by gluing text onto&, so BEM suffixing has no native equivalent. -
Specificity differs. Native
&takes the specificity of the most specific selector in the parent list (like:is()), whereas Sass simply pastes the selector text in, so the two can compute different specificity for the same-looking rule. -
Output size. Sass flattens at compile time and ships plain, widely-compatible selectors; native nesting ships the nesting itself, which is smaller over the wire but requires browser support.
Since Sass compiles nesting away entirely, the two do not conflict: a Sass file can use Sass nesting throughout and still emit CSS that runs anywhere.