Where I use CSS nesting and where I keep selectors flat
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.
Enjoyed reading this?
If you found this article helpful, consider supporting my writing. Every little bit helps keep this blog going.
Related articles
- Making Links Easy to Recognize
Make links recognisable with underlines, clear focus states, useful text, and behaviour that does not surprise the reader.
- When CSS Layout Order Stops Matching the HTML
Keep Grid and Flexbox layouts aligned with the HTML order so keyboard focus and screen reader reading do not jump around the page.
- Keyboard Focus Styles With :focus-visible
Keep a clear keyboard focus indicator while avoiding the same focus ring after every pointer interaction.