Nesting & the Parent Selector
In plain CSS you write .menu, then .menu a, then .menu a:hover, repeating .menu on every line. Sass nesting lets you write the child rules inside the parent rule, so the structure of your stylesheet follows the structure of your HTML and the parent name is typed once.
In this lesson you will nest selectors, use the parent selector & for hover states, modifiers and BEM class names, nest media queries, and learn how Sass nesting differs from the nesting now built into CSS.
Basic nesting
Write a rule inside another rule, and Sass joins the two selectors with a space, which in CSS means "descendant of":
.site-nav {
background: #1e3a8a;
padding: 0.5rem 1rem;
ul {
list-style: none;
margin: 0;
padding: 0;
}
a {
color: white;
text-decoration: none;
}
}.site-nav {
background: #1e3a8a;
padding: 0.5rem 1rem;
}
.site-nav ul {
list-style: none;
margin: 0;
padding: 0;
}
.site-nav a {
color: white;
text-decoration: none;
}Combinators work as well. Start the nested selector with >, + or ~ and Sass places it between the two selectors: > li inside .menu becomes .menu > li.
The parent selector &
& stands for "the full selector of the parent rule". Where you put it decides where the parent goes. The common uses:
&:hover,&:focus-visible,&::before: add a pseudo-class or pseudo-element to the parent (no space).&.is-open: the parent element also has another class.&--featured,&__title: join text to the parent name to make a new class name. Native CSS cannot do this..is-active &: put something before the parent, for example a state class on an ancestor.
.menu {
list-style: none;
> li {
display: inline-block;
}
a {
color: #1e3a8a;
&:hover,
&:focus-visible {
text-decoration: underline;
}
.is-active & {
font-weight: 700;
}
}
&.is-open {
display: block;
}
&__badge {
background: #f59e0b;
border-radius: 999px;
}
}.menu {
list-style: none;
}
.menu > li {
display: inline-block;
}
.menu a {
color: #1e3a8a;
}
.menu a:hover, .menu a:focus-visible {
text-decoration: underline;
}
.is-active .menu a {
font-weight: 700;
}
.menu.is-open {
display: block;
}
.menu__badge {
background: #f59e0b;
border-radius: 999px;
}Without &, :hover inside a would compile to .menu a :hover with a space, which means "any hovered element inside the link", a classic beginner bug. The &__badge rule became the separate class .menu__badge, which is the basis of the BEM naming style in BEM with Sass.
When the parent rule has several selectors, & repeats the work for each one: .btn, .link { &-primary { ... } } produces .btn-primary, .link-primary.
Nesting media queries
You can put @media inside a rule. Sass moves it out to the top level and wraps the parent selector inside it, so each component keeps its responsive styles next to its normal styles:
.gallery {
display: grid;
gap: 1rem;
grid-template-columns: 1fr;
@media (width >= 40rem) {
grid-template-columns: repeat(2, 1fr);
}
@media (width >= 64rem) {
grid-template-columns: repeat(4, 1fr);
gap: 1.5rem;
}
}.gallery {
display: grid;
gap: 1rem;
grid-template-columns: 1fr;
}
@media (width >= 40rem) {
.gallery {
grid-template-columns: repeat(2, 1fr);
}
}
@media (width >= 64rem) {
.gallery {
grid-template-columns: repeat(4, 1fr);
gap: 1.5rem;
}
}Here is the compiled CSS on a page. Make the preview wider and narrower to watch the columns change:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Nested media queries</title>
<style>
body { font-family: system-ui, sans-serif; padding: 1rem; }
.tile { background: #dbeafe; padding: 1.5rem; border-radius: 6px; text-align: center; }
/* compiled from the SCSS above */
.gallery {
display: grid;
gap: 1rem;
grid-template-columns: 1fr;
}
@media (width >= 40rem) {
.gallery {
grid-template-columns: repeat(2, 1fr);
}
}
@media (width >= 64rem) {
.gallery {
grid-template-columns: repeat(4, 1fr);
gap: 1.5rem;
}
}
</style>
</head>
<body>
<div class="gallery">
<div class="tile">Hawa Mahal</div>
<div class="tile">Amber Fort</div>
<div class="tile">Jal Mahal</div>
<div class="tile">City Palace</div>
</div>
</body>
</html>Sass also has property nesting for properties that share a prefix, such as font: { family: system-ui; size: 1rem; }, which becomes font-family and font-size. It works but is rarely used, because it saves little and confuses readers.
Keep declarations before nested rules
Write a rule's own declarations first and its nested rules after. Since Dart Sass 1.92, if you put a declaration after a nested rule, Sass keeps your order exactly, which means it has to start a second copy of the parent selector:
.card {
padding: 1rem;
h3 {
margin: 0;
}
border: 1px solid #ddd;
}.card {
padding: 1rem;
}
.card h3 {
margin: 0;
}
.card {
border: 1px solid #ddd;
}The output is correct but has two .card rules. Moving border above h3 gives one tidy rule.
Sass nesting vs native CSS nesting
All current browsers now support nesting in plain CSS, with the same &. The main differences:
| Sass nesting | Native CSS nesting | |
|---|---|---|
| Works without a build step | No | Yes |
&:hover, .parent &, > li | Yes | Yes |
Join text: &__title, &--big | Yes | No |
| Output | Flat CSS that every browser reads | Nested CSS, read by the browser |
Specificity of .a, #b { .c {} } | Each selector keeps its own specificity | Wrapped in :is(), so all get the highest |
The last row is subtle: native CSS treats a nested selector list like :is(.a, #b) .c, which takes the specificity of the strongest item (#b). Sass writes out .a .c, #b .c, so each keeps its own. If you move Sass code to native nesting, check for selectors that suddenly win or lose. The CSS course covers native nesting in CSS Nesting.
Common mistakes
- Nesting too deep.
.page .main .content .card .title ais long, fragile and hard to override. Keep to three levels; use a class on the element instead. - Forgetting
&before a pseudo-class.:hoverwithout&adds a space and selects descendants. - Mirroring the whole HTML tree. Nesting should group related styles, not copy the markup structure.
- Declarations after nested rules. They work, but they split the rule in two. Put declarations first.
Key points
- A nested rule becomes a descendant selector;
>,+and~at the start of a nested selector become combinators. &is the parent selector:&:hoveradds a state,&.classadds a class,.state &puts something before the parent.- Only Sass can join text to
&(&__title,&--big), which makes BEM class names easy. - Nested
@mediarules are moved to the top level with the parent selector inside. - Write declarations before nested rules, and keep nesting to about three levels.
Exercise
Write SCSS for a Rohtak cricket club's fixture table using nesting. .fixtures should have width: 100%. Nest th (left-aligned, bottom border 2px solid #166534), td (padding 0.5rem) and tr with an &:nth-child(even) rule that sets background #f0fdf4. Add a &--compact modifier that sets the td padding to 0.25rem, and a nested media query that sets font-size: 0.875rem below 30rem.
Show solution
.fixtures {
width: 100%;
th {
text-align: left;
border-bottom: 2px solid #166534;
}
td {
padding: 0.5rem;
}
tr:nth-child(even) {
background: #f0fdf4;
}
&--compact td {
padding: 0.25rem;
}
@media (width < 30rem) {
font-size: 0.875rem;
}
}&--compact td compiles to .fixtures--compact td, a separate modifier class, and the media query is moved out to the top level with .fixtures inside it.
Quick Quiz
1. What does .card { &:hover { ... } } compile to?
& is replaced by the parent selector with no space, so the pseudo-class attaches to .card.
2. What does .btn { .dark-mode & { ... } } compile to?
Writing & after another selector puts the parent at that position.
3. Which can Sass nesting do that native CSS nesting cannot?
Native CSS nesting cannot build new class names by joining text to &.
4. Why keep nesting shallow?
Each level adds to the selector length and specificity in the output.
Learn Sass with a mentor
Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
