CSS Nesting Is Useful When It Keeps Context Close
Native CSS nesting is available in modern browsers, but that does not make every nested selector a good idea. I use it when the nested rule only makes sense beside its parent—usually a state, pseudo-element, or media query.
If a selector reads clearly on its own, I keep it flat. That keeps specificity predictable and makes the class easier to find.
States belong with the component
Pseudo-classes, pseudo-elements, and attribute states are a natural fit:
.button {
background: blue;
color: white;
&:hover {
background: darkblue;
}
&::before {
content: "→";
margin-right: 0.5em;
}
}
The same applies to an accessibility state:
.nav-item {
color: #666;
&[aria-current="page"] {
color: #000;
font-weight: bold;
}
}
Both nested rules describe the same component rather than a new element in its DOM tree.
Media queries can stay beside the rule
For a small component, nesting its responsive change avoids splitting one idea across the stylesheet:
.header {
padding: 1rem;
font-size: 1.2rem;
@media (min-width: 768px) {
padding: 2rem;
font-size: 1.5rem;
}
}
Parent-dependent themes can also be readable when the relationship is shallow:
.card {
background: white;
.theme-dark & {
background: #222;
}
}
Keep component classes flat
I would not use nesting just to construct a BEM class:
.card {
&__title {
font-size: 1.5rem;
}
}
Write the class directly instead:
.card__title {
font-size: 1.5rem;
}
Generic descendant selectors are another warning sign:
.card {
heading-section {
margin-bottom: 1rem;
}
}
Prefer an explicit component class:
.card__heading {
margin-bottom: 1rem;
}
Deep nesting has the same problem as long descendant selectors: the CSS starts depending on the exact HTML structure. A flat modifier is usually easier to reuse:
/* Better */
.button--large {
font-size: 1.2rem;
}
/* Avoid */
.sidebar .button {
font-size: 1.2rem;
}
A compact pattern I like
.dropdown {
position: relative;
&[aria-expanded="true"] {
/* ... */
}
&:hover {
/* ... */
}
@media (min-width: 768px) {
/* ... */
}
}
This keeps the dropdown’s states and breakpoint close without encoding a chain of child elements. One nesting level is not a browser rule, but it is a useful default for humans.
Nesting is syntax, not architecture. It makes good component boundaries more compact and bad selector hierarchies easier to write. If I have to trace several levels to understand the final selector, I flatten it.