CSS Nesting
Variables named your values. calc and friends computed them. Nesting organizes the selectors that use those values: keep a card’s title, button, and hover styles inside .card instead of repeating the parent name across the file.
Nest a child selector
Write a rule inside another rule. A nested class or type selector is joined as a descendant of the parent (space combinator) unless you say otherwise with &:
.card {
padding: 1.25rem;
border-radius: 0.75rem;
background: var(--surface, #fff);
.title {
margin: 0 0 0.5rem;
font-size: 1.25rem;
}
.body {
color: var(--muted, #444);
line-height: 1.5;
}
}
The browser expands this to .card .title and .card .body. You still get ordinary descendant selectors — nesting is sugar for structure and locality, not a new matching model.
The & parent selector
Use & when the nested rule must attach to the same element as the parent — pseudo-classes, pseudo-elements, or an extra class on the parent:
.button {
display: inline-flex;
padding: 0.6rem 1.1rem;
border: 0;
border-radius: 0.5rem;
background: var(--brand, #2563eb);
color: #fff;
&:hover {
background: var(--brand-hover, #1d4ed8);
}
&:focus-visible {
outline: 2px solid var(--brand, #2563eb);
outline-offset: 2px;
}
&.is-primary {
background: var(--brand-strong, #1e40af);
}
}
&:hover means .button:hover. &.is-primary means .button.is-primary. Without &, a bare :hover { } is invalid nesting in native CSS — the & makes the attachment explicit.
Combinators with &
Put the combinator after & when you want a child, sibling, or other explicit link — not a plain descendant space:
.nav {
display: flex;
gap: 0.75rem;
& > .item {
padding: 0.35rem 0.6rem;
}
& > .item + .item {
border-inline-start: 1px solid #ddd;
}
/* same as nesting .item without & — descendant */
.badge {
font-size: 0.75rem;
}
}
& > .item → .nav > .item. A nested .badge without & → .nav .badge. Choose intentionally: deep accidental descendants are a common nesting smell.
Nest @media with the component
You can nest at-rules such as @media inside a style rule so breakpoints live next to the component they change:
.hero {
padding: 2rem 1.25rem;
h1 {
font-size: clamp(1.75rem, 1.2rem + 2vw, 2.75rem);
}
@media (min-width: 48rem) {
padding: 3rem 2rem;
h1 {
max-width: 20ch;
}
}
}
Expanded, the media query still wraps the declarations that belong to .hero (and .hero h1). Pair this with tokens and clamp() from earlier Modern Toolkit lessons — nesting only relocates the rules.
Nesting patterns at a glance
📊 Native nesting cheatsheet
| You write | It means | Use when |
|---|---|---|
.card { .title { } } |
.card .title |
Style a descendant under the component |
.btn { &:hover { } } |
.btn:hover |
Pseudo-class on the same element |
.btn { &.big { } } |
.btn.big |
Extra class on the same element |
.nav { & > a { } } |
.nav > a |
Explicit child combinator |
.box { @media (…) { } } |
Media query scoped to that rule’s selectors | Keep breakpoints with the component |
Keep nests shallow
Two or three levels usually stay readable. Deeper trees recreate the “selector pyramid” problem Sass teams hit: hard diffs, higher specificity, and styles that only make sense inside one giant parent. Prefer a clear component root (.card, .nav) and nest states and close children — not the whole page.
Component bundle: tokens + nest + states
A realistic slice ties the last three toolkit ideas together without repeating the calc or variables lessons:
.panel {
--panel-pad: 1rem;
padding: var(--panel-pad);
border: 1px solid #e5e7eb;
border-radius: 0.75rem;
.heading {
margin: 0 0 0.5rem;
font-weight: 600;
}
.actions {
display: flex;
gap: calc(var(--panel-pad) / 2);
margin-block-start: var(--panel-pad);
}
&.is-compact {
--panel-pad: 0.5rem;
}
@media (max-width: 36rem) {
.actions {
flex-direction: column;
}
}
}
Tokens still live as custom properties; calc still derives a gap; nesting keeps heading, actions, compact, and the small-screen tweak in one place.
Try it yourself
- Take flat rules
.card {},.card .title {},.card .meta {}and rewrite them with nesting under.card. - Add
&:hoveron a.linkso only the link (not a nested span) changes color. - Nest
@media (min-width: 40rem)inside.sidebarto widen padding — confirm DevTools shows the expanded selectors. - Convert a mistaken nested
:hover { }(no&) into&:hover { }and note that it validates. - Limit a messy three-level nest to two levels by promoting an inner block to its own class.
Common mistakes
- Writing
:hover { }nested without&— invalid or not what you meant; use&:hover - Expecting bare
.childto mean.parent.child— that needs&.child - Copying Sass
&__elementBEM hacks blindly — native nesting follows CSS selector grammar - Nesting five levels deep “because we can” — readability and specificity suffer
- Thinking nesting replaces
var()orcalc()— it only groups selectors and at-rules
Summary
- Nest child selectors to keep component styles local; they expand to descendants by default
- Use
&for same-element states (:hover,.is-active) and explicit combinators - Nest
@mediabeside the component you are tuning - Keep nests shallow; nesting is structure, not a design-token or math feature
- Next in Modern Toolkit: container queries — style from parent size, not only the viewport
🧠 Test Your Knowledge
Test Your Knowledge
Challenge yourself with this interactive quiz and see how well you understand the topic
📝 Instructions
- Read each question carefully
- Select the best answer for each question
- You can retake the quiz as many times as you want
- Your progress will be shown at the top