Quick answer: Native CSS nesting lets you write child selectors inside a parent rule, using & for the parent reference, so stylesheets read like their markup without a preprocessor. Cascade layers (@layer) let you declare the order in which groups of styles win, which ends the specificity arms race — later layers beat earlier ones regardless of selector weight.
Table of Contents
CSS nesting: native, no build step
Nesting arrived in CSS proper and has been widely available since 2023. It looks like Sass, but the browser parses it directly.
.card {
padding: 1rem;
border-radius: 0.5rem;
& h2 { margin-block-start: 0; }
&:hover { box-shadow: 0 4px 12px rgb(0 0 0 / 0.08); }
&.featured { border: 2px solid var(--brand, teal); }
.badge { font-size: 0.75rem; }
}
The & symbol means “the parent selector”, so &:hover compiles to .card:hover and &.featured to .card.featured. Nested selectors without & are descendants, exactly as you would write them flattened.
- Keep it shallow. Two or three levels is readable; five is a specificity trap. If you need to nest a nested rule, reconsider the component boundary.
- Nesting changes specificity.
.card &:hoveris more specific than.card:hover, so watch what you prepend. - No variables or mixins. Nesting is only about selectors. For values, use custom properties.
Cascade layers end specificity fights
Normally a heavier selector beats a lighter one no matter where either appears. Layers flip that: you declare the order of layers, and within that order, a rule in a later layer wins over one in an earlier layer — even if the earlier selector was more specific.
@layer reset, base, components, utilities;
@layer base {
h2 { font-size: 1.5rem; color: #334155; }
}
@layer components {
.card h2 { font-size: 1.25rem; } /* wins over base, despite lower specificity */
}
@layer utilities {
.text-lg { font-size: 1.75rem !important; }
}
The first line declares the order once. After that, files can add rules to a layer from anywhere, and you never have to reason about which selector is heavier. A utility layer placed last reliably overrides everything before it, which is how frameworks such as Tailwind organise their output.
Organising a stylesheet with CSS nesting
| Layer | Contains | Overridable? |
|---|---|---|
reset | Normalise margins, box-sizing | Always — first |
base | Element defaults: body, headings, links | By components |
components | Buttons, cards, navigation | By utilities |
utilities | Single-purpose helpers | Last word |
Adopting layers does not mean rewriting a site. Add the @layer declaration at the top of one entry stylesheet and wrap your existing rules into the closest layer. Third-party CSS can be assigned to its own layer, giving you an easy override without !important.
@import url("third-party.css") layer(vendor);
That single layer(vendor) puts the whole dependency below your styles, so your rules win without guessing at its specificity.
Next: transitions and animations. To learn modern CSS with a mentor, Ampersand Academy teaches web development one-to-one.
Frequently asked questions
Is CSS nesting the same as Sass nesting?
The syntax is similar but native CSS nesting runs in the browser with no build step. It handles selectors only, so variables and mixins still belong to a preprocessor or to custom properties.
What does the ampersand mean in CSS nesting?
It refers to the parent selector. Writing &:hover inside .card produces .card:hover, and &.featured produces .card.featured.
How do cascade layers work?
You declare an order with @layer, and a rule in a later layer beats one in an earlier layer regardless of selector specificity, which removes the need for escalating selectors.
Do I still need !important with layers?
Less often. Utilities placed in a final layer reliably override earlier layers, so many uses of !important disappear. It still has a role for third-party code you cannot restructure.
How do I put a library into a cascade layer?
Import it with @import url(…) layer(vendor). The import lands in that layer, so your own styles always take precedence over it.

