native css nesting

[check: …]
📖 Pair this live Tailwind demo with the companion guide (.md) — this page is the rendered ground truth. ↗ Builds on functional @utility — nesting is what lets one @utility own its hover, children, and ::before/::after in a single block.

Sass nesting → native CSS nesting: same syntax, no compiler

CSS Nesting (the & nesting selector) shipped natively in all evergreen browsers by late 2023. The syntax is nearly identical to Sass — nest a selector inside a parent rule, and & refers to that parent. The difference: no build step. Tailwind v4 leans on this hard — its engine (Lightning CSS) desugars nesting for you everywhere it appears, so a custom @utility can co-locate its hover state, child styles, and pseudo-elements in ONE block instead of scattering flat rules.

/* SASS — needs a compiler / preprocessor */ .card { padding: 1rem; &:hover { box-shadow: 0 4px 12px rgba(0,0,0,.2); } & .title { font-weight: 700; } } /* NATIVE CSS — identical output, zero build step. Works in Tailwind v4. */ .card { padding: 1rem; &:hover { box-shadow: 0 4px 12px rgba(0,0,0,.2); } & .title { font-weight: 700; } } /* both compile to: .card { padding: 1rem } .card:hover { box-shadow: 0 4px 12px rgba(0,0,0,.2) } .card .title { font-weight: 700 } */
aspectSass nestingnative CSS nesting
build step required — sass/dart-sass compiles to CSS none — the browser parses & flattens nesting directly
the & selector refers to the parent selector (Sass interpolation) identical — refers to the parent rule's matched element
specificity exact flat selector (.card .title) flat, but parent is wrapped in :is() in reversed/complex cases (takes max specificity)
in Tailwind v4 use only via a separate Sass pipeline (not integrated) first-class — works inside @utility, @layer, plain CSS in <style type="text/tailwindcss">
BEM suffix &__elem fully supported, idiomatic supported in modern browsers (late spec addition) — works, but historically spotty

1 · @utility card-nest — one utility, four nested rules (gold-check)

This is the whole lesson in one block. The custom utility card-nest owns its &:hover lift, its & .nk-title / & .nk-body descendants, and its &::before star + &::after accent bar. None of these are separate flat selectors — they are nested inside the utility. Hover the card to feel the &:hover rule fire.

@utility card-nest { position: relative; display: flex; align-items: center; gap: 0.85rem; padding: 1rem 1.25rem; /* …base styles… */ &:hover { transform: translateY(-3px); box-shadow: …; } /* the element itself */ & .nk-title { font-weight: 700; letter-spacing: 0.06em; } /* a descendant */ &::before { content: "★"; font-size: 1.5rem; } /* pseudo-element */ &::after { content: ""; position: absolute; bottom: 0; } /* pseudo-element */ }
nested card title
hover me — the lift, glow, and border shift all come from &:hover.
live measurements (from getComputedStyle):
::before content: —
.nk-title font-weight: —
.card-nest:hover rule emitted: —
::after bottom bar height: —

The gold-check asserts all three nesting channels compiled: ::before carries the star, .nk-title carries font-weight: 700, and a literal .card-nest:hover rule exists in the live stylesheet. Before Tailwind's JIT runs, all three are false — they flip together, which is the proof.

2 · the & selector — every position it can appear

& is short for "the parent rule's selector". It can lead (most common), trail (to reverse context), repeat (sibling chains), or sit inside compound selectors. The one place it cannot reach is a pseudo-element of a reversed parent — see the gotchas panel.

you write (nested)desugars tomeaning
&:hover { … } .card:hover { … } style the element ITSELF in a state
& .title { … } .card .title { … } descendant (note the space) — implicit if you omit &
& > .child { … } .card > .child { … } direct child only
&::before { … } .card::before { … } generate/box pseudo-element of the parent
.featured & { … } .featured .card { … } trailing & reverses context (parent as descendant)
& + & { … } .card + .card { … } repeat & — adjacent sibling of same kind
bare :hover { … } .card *:hover { … } GOTCHA — a bare pseudo (no &) targets DESCENDANTS, not self

Why this matters in Tailwind: inside @utility card-nest, writing :hover { … } instead of &:hover { … } would style hovered descendants of every card — silently wrong. Always prefix state pseudo-classes with &.

3 · where nesting works inside Tailwind v4

Tailwind v4 processes your whole CSS source through Lightning CSS, so nesting desugars anywhere a real CSS rule can live. The only place it does not belong is @theme (that block holds variables only).

contextnesting supported?emitted selector example
@utility name { &:hover } ✅ yes — idiomatic for custom utilities .name:hover (variant-friendly: hover: stacks)
@layer components { .x { & .y } } ✅ yes .x .y inside the components layer
plain rule .tag-pill { & > b } ✅ yes — see the live demo below .tag-pill > b
@theme { &:hover } ❌ no — @theme is variables only — (move the rule to @utility / @layer)
inside @custom-variant ⚠️ variants define selector lists, not nested rules — use & as a placeholder there (different meaning) @custom-variant pointer-coarse (&:hover)

Plain-CSS nesting in action — .tag-pill (not a utility, just a class) with nested & > b and & > span:

.tag-pill { display: inline-flex; gap: 0.4rem; /* … */ & > b { color: oklch(0.92 0.02 250); font-weight: 600; } & > span { color: oklch(0.82 0.16 195); font-size: 0.75rem; } }
v4.3native nesting   Lightning CSSdesugars &   Baselinesince Dec 2023

4 · browser support — Baseline widely available

Native CSS Nesting reached Baseline "Widely available" in December 2023. Tailwind v4 targets this baseline, so nesting is safe to ship without a fallback in any v4-supported browser.

browsermin versionshipped
Chrome / Edge112+April 2023
Safari (desktop + iOS)16.5+May 2023
Firefox117+August 2023
Samsung Internet22+2023
Lightning CSS (Tailwind v4's engine) desugars nesting regardless of target — so even the @tailwindcss/browser@4 CDN flattens it before the browser sees it.

intent → pattern

intentpatternwhy
co-locate a utility's hover/focus/active @utility btn { &:hover { … } &:focus-visible { … } } one block, no scattered flat selectors; variants still stack
style a child of a custom utility & .label { font-weight: 700 } descendant via space; & > .label for direct child
add a decorative pseudo-element &::before { content: "★" } must use & — bare ::before is invalid nested
style self only in an ancestor context .dark & { … } trailing & reverses context (no dark: variant needed)
scope a media query to one selector & { @media (width < 600px) { … } } at-rules nest too; the properties are implicitly wrapped in &
avoid the silent bare-pseudo bug write &:hover, never bare :hover bare :hover styles descendants — the #1 nesting footgun