CSS Nesting: How the Ampersand Selector Works
Native CSS nesting introduces the ampersand (&)
symbol as a dedicated nesting selector to explicitly reference the
parent selector within a rule block. This article explains how the
browser resolves the & selector, how to apply
pseudo-classes, compound selectors, and contextual styling, as well as
the critical differences between native CSS nesting and preprocessor
syntax like Sass.
Representing the Parent Selector
In native CSS, the & selector explicitly resolves to
the parent selector rule list wrapped in an :is()
pseudo-class. When placed at the start of an inner rule, it directly
targets child elements or states of the parent.
.card {
padding: 1rem;
& p {
color: #333;
}
}This compiles conceptually to :is(.card) p. While modern
CSS allows implicit nesting for element tags without the
& symbol, explicitly using & clarifies
intent and avoids parsing ambiguities.
Adding Pseudo-Classes, Pseudo-Elements, and Modifiers
The primary use case for & is targeting states and
modifiers without whitespace between the selector and the ampersand.
Pseudo-classes and pseudo-elements:
.button { background: blue; &:hover { background: darkblue; } &::after { content: ""; } }Compound class selectors:
.button { &.is-active { border: 2px solid green; } }
Placing the & directly against :hover
or .is-active ensures the output targets the parent itself
under those conditions (.button:hover and
.button.is-active), rather than looking for a descendant
element.
Contextual and Ancestor Reordering
The & selector does not have to appear first in a
nested declaration. Placing & later in the selector
rule changes the context based on an ancestor:
.card {
color: black;
.dark-theme & {
color: white;
}
}This compiles to :is(.dark-theme) :is(.card), applying
the styles to .card only when it is inside
.dark-theme.
No Identifier Concatenation (The BEM Limitation)
Unlike preprocessors such as Sass or Less, native CSS cannot use the ampersand for string concatenation. The following BEM pattern is invalid in native CSS:
/* This does NOT work in native CSS */
.block {
&__element {
color: red;
}
}Because CSS parses selectors as dynamic object tokens rather than
plain strings, & represents the entire parsed parent
selector, not a string fragment. To target elements using BEM, full
selector names must be declared.
Specificity and the
:is() Behavior
Because the & selector resolves using
:is(), it adopts the highest specificity of any selector
within the parent's selector list.
.nav, #main-nav {
& a {
color: blue;
}
}In this scenario, & a expands to
:is(.nav, #main-nav) a. Consequently, the link inside
.nav will inherit the high specificity of the
#main-nav ID selector, even if the element currently
matches only the .nav class.