skip to content

In Sass, `.card { &__title { } }` compiles to `.card__title`. Why does that pattern not work with native CSS nesting, and what forms of & are valid there?

level: middleimportance: should knowfreq 45%

answer

  1. one is text, one is a selector
  2. no string phase in the browser
  3. invalid selector drops the whole rule
  4. compounds and combinators, never names
  5. state nesting survives, BEM suffixes do not

basics

~20 s

Sass treats & as text and glues it to the suffix, producing .card__title. Native CSS & is a real selector that cannot be concatenated with an identifier, so &__title is an invalid selector and the browser drops the rule.

solid answer

~50 s

The two ampersands are different things. Sass's `&` is a textual placeholder resolved by a compiler, so `&__title` is string concatenation that emits `.card__title` — a selector that has nothing to do with `.card` at match time. Native CSS's `&` is a genuine simple selector evaluated by the browser; there is no string phase, so it cannot be fused with a suffix. `&__title` parses as the compound `&` followed by a type selector `__title`, which is invalid, and an invalid selector makes the browser discard the whole rule. What *is* valid is anything you could legally compound or combine with a real selector: `&:hover`, `&.is-active`, `&[disabled]`, `& .title`, `& > li`, `.theme-dark &`, `& + &`. So BEM-style suffix generation is the one Sass nesting idiom with no native equivalent — you have to write `.card__title` out in full.

code

css · 16 lines
css
/* Invalid in native CSS — the whole rule is discarded */
.card {
  &__title { font-weight: 700; }
  &--large { padding: 2rem; }
}

/* Valid: & compounds with attachments, combines with relations */
.card {
  &.is-selected  { outline: 2px solid; }
  &:focus-within { border-color: currentColor; }
  & > .title     { font-weight: 700; }
  .theme-dark &  { background: #111; }
}

/* BEM names must be written out */
.card__title { font-weight: 700; }

go deeper

for a junior

Remember that BEM suffixes like &__title only work in Sass. In plain CSS, write the full class name; & can only be combined with real selectors such as :hover or .is-active.

for a middle

Explain why: Sass substitutes text before the browser runs, while native & is a selector the engine evaluates, and gluing it to an identifier produces an invalid selector that CSS error handling discards.

for a senior

Show that you can spot the failure mode fast — a silently dropped rule during a Sass-to-native migration — and that you know which idioms port cleanly versus which need rewriting.

for a principal

Own the tradeoff a team faces here: losing suffix concatenation costs terseness but restores greppable class names, which changes how safely a large stylesheet can be refactored and deleted from.

## Two different ampersands Sass runs before the browser sees anything. Its `&` is a token the compiler substitutes textually, which is why suffixing works: `&__title` inside `.card` produces the literal string `.card__title`. The emitted selector is a plain class selector; the relationship to `.card` exists only in your source file, not in the cascade. Native CSS nesting has no compiler. `&` is a **selector** that the engine evaluates against the DOM, standing for the enclosing rule's selector list. Selectors are made of simple selectors joined by combinators; there is no operation in the selector grammar that concatenates two selectors into a new identifier. So the Sass suffix trick has no native counterpart — not because browsers chose not to support it, but because nothing in the model could express it. ## What actually happens to &__title ```css .card { &__title { color: red; } /* invalid — rule discarded */ } ``` The tokenizer sees `&` followed immediately by the identifier `__title`, i.e. a compound selector consisting of the nesting selector and a type selector. A type selector is only allowed as the *first* component of a compound, so the whole selector is invalid, and CSS's error handling throws away the entire rule — silently. The same fate meets `&-active`, `&_wrapper`, and `&--large`. Nothing is logged in normal browsing; devtools show the rule struck through or absent, which is the quickest way to confirm it. ## What is valid Anything that is a legal selector once `&` is treated as a simple selector: ```css .btn { &:hover { … } /* .btn:hover — compound with a pseudo-class */ &.is-active { … } /* .btn.is-active — compound with a class */ &[disabled] { … } /* .btn[disabled] — compound with an attribute */ & .icon { … } /* .btn .icon — descendant */ & > .label { … } /* .btn > .label — child */ & + & { … } /* .btn + .btn — adjacent siblings */ .theme-dark & { … } /* .theme-dark .btn — contextual */ } ``` Note the pattern: `&` compounds with things that *attach* to an element (class, ID, attribute, pseudo-class) and combines with things that *relate* elements (descendant, child, sibling). It never builds a name. ## Consequences for BEM BEM stylesheets written in Sass lean hard on suffixing, both for elements (`&__title`) and modifiers (`&--large`). Ported to native nesting, those blocks must be written out: ```css .card { padding: 1rem; } .card__title { font-weight: 700; } .card--large { padding: 2rem; } ``` That is arguably the honest form — a grep for `.card__title` now finds the rule, which the Sass version famously prevented — but it does mean native nesting does not compress a BEM sheet the way Sass did. The idiom that survives is *state* nesting: `.card { &.is-selected { … } }` is a compound, so it works natively and keeps the class name greppable in full. ## Other differences worth naming Beyond concatenation, the same source can behave differently in the two systems: - **Specificity.** Sass emits each branch of a parent selector list separately; native `&` scores like `:is()`, taking the highest specificity in the list. A heterogeneous parent list therefore raises the score of everything nested under it. - **Sass-only syntax.** `//` comments, `$variables`, `@mixin`/`@include`, `@extend`, nested properties (`font: { size: 1rem; }`) and Sass functions have no native equivalent; custom properties replace some variable uses but not mixins. - **Runtime resolution.** Native nesting is parsed by the browser, so the nested source is what ships and what devtools show — there is no flattened output file to inspect. ## The check to run When a nested rule silently does nothing, the first question is whether the selector is valid at all. Paste the flattened form into `document.querySelectorAll` or the devtools search box; if it throws or matches nothing, you have a parse error rather than a cascade problem — and a `&` glued to an identifier is the most common cause in a codebase migrating away from Sass.

  • How would you tell, in the browser, that `&__title` was dropped rather than simply outranked?
    Open the element in devtools: an outranked declaration appears struck through under the winning one, while a dropped rule does not appear in the matched-rules list at all, and the stylesheet view shows the selector as invalid. Running `document.querySelectorAll('.card__title')` also distinguishes a missing rule from a missing element.
  • Which common Sass nesting idioms survive the move to native CSS unchanged?
    State and variant compounds (`&:hover`, `&.is-open`, `&[aria-expanded="true"]`), structural nesting (`& .title`, `& > li`), contextual nesting (`.theme-dark &`), and nesting a `@media` block inside a rule. What does not survive is anything that builds a new class name from `&`, plus mixins, `@extend`, `$` variables and `//` comments.
  • Is losing suffix concatenation purely a downside?
    Not entirely. Suffix generation is the reason a Sass codebase can contain `.card__title` styling that no search for that class ever finds, which makes deletion and refactoring risky. Writing the full class name restores greppability; the cost is more typing and a flatter file that no longer visually groups a block with its elements.

saying these in an interview costs you the question

  • Expecting & to build class names in native CSS
  • Assuming an invalid nested selector just fails locally
  • Thinking browsers will add Sass-style concatenation soon
  • Believing all Sass nesting ports over unchanged
  • Confusing &.is-active with & .is-active

context