DEV Community

Artclick
Artclick

Posted on

Stop Compiling Your Nested CSS. The Browser Does It Now

For years, nesting selectors inside each other was one of the main reasons teams reached for Sass in the first place. You'd write .card { .title { ... } }, run it through a compiler, and get flat CSS out the other end. It was one of those "why doesn't the browser just do this" features, and honestly, it's a little wild that it took this long. But native CSS nesting is here now, it's Baseline in 2026, meaning it works across Chrome, Edge, Firefox, and Safari without a build step, and it's worth actually learning properly instead of just copy-pasting Sass habits into it and hoping for the best.

That second part matters more than people expect. Native nesting looks almost identical to Sass nesting on the surface, but the parsing rules underneath are stricter, and a couple of the differences will genuinely trip you up the first time you hit them. This guide walks through the syntax, the & selector, nesting at-rules like @media, and the handful of gotchas worth knowing before you delete your Sass dependency.

Browser support, briefly

Native CSS nesting is supported by Chrome, Edge, Firefox, and Safari as of 2026, and it's considered Baseline Widely Available. The one thing worth knowing is that there were two versions of the spec: an early, stricter one that required the & symbol in more places, and a later "relaxed" version that infers it automatically in most cases. All current browser versions support the relaxed syntax, so unless you specifically need to support an old browser version, you can write nesting the way this guide shows it.

If you do need a safety net for older environments, wrap your nested rules in a feature query:

@supports (selector(&)) {
  /* nested rules go here */
}
Enter fullscreen mode Exit fullscreen mode

Browsers that don't understand nesting will drop the unsupported rules entirely rather than ignoring just the nesting part, so this is worth doing if any real chunk of your audience is on an older browser. For most projects in 2026 though, you probably don't need it.

The basic syntax

Here's the simplest possible example. Instead of writing this the old, flat way:

.card {
  border-radius: 8px;
  padding: 1rem;
}

.card h2 {
  font-size: 1.25rem;
}

.card p {
  color: #4b5563;
}
Enter fullscreen mode Exit fullscreen mode

You write this:

.card {
  border-radius: 8px;
  padding: 1rem;

  h2 {
    font-size: 1.25rem;
  }

  p {
    color: #4b5563;
  }
}
Enter fullscreen mode Exit fullscreen mode

Both compile down to exactly the same thing. The nested h2 rule is understood as .card h2, and the nested p rule as .card p. No ampersand needed for this case, since element selectors like h2 and p are unambiguous, the browser knows you mean "an h2 inside .card," not "a property called h2."

The & selector, and when you actually need it

The & symbol represents the parent selector, and while it's optional in a lot of cases under the relaxed syntax, there are specific situations where you still need it.

You need it when combining with the parent to form a compound selector. If you want to target .card itself when it also has a .featured class, you can't just nest .featured on its own, that would mean .card .featured (a descendant), not .card.featured (the same element with both classes). You need the ampersand directly against it:

.card {
  border: 1px solid #e5e7eb;

  &.featured {
    border-color: #6366f1;
  }
}
Enter fullscreen mode Exit fullscreen mode

You need it for pseudo-classes and pseudo-elements, though in practice most people write these with & anyway even where it's technically optional, since it reads more clearly:

.card {
  transition: box-shadow 0.2s ease;

  &:hover {
    box-shadow: 0 4px 12px rgb(0 0 0 / 0.1);
  }

  &::before {
    content: "";
    display: block;
  }
}
Enter fullscreen mode Exit fullscreen mode

You need it, doubled up, for combinators like adjacent siblings. This one surprises people. If you want to select a sibling element that comes right after the current selector, you write the ampersand twice, once to represent the parent, and again as the actual sibling combinator:

.card {
  & + & {
    margin-top: 1rem;
  }
}
Enter fullscreen mode Exit fullscreen mode

That compiles to .card + .card, styling a card that immediately follows another card. It looks strange the first time you see it, but it makes sense once you remember & is just standing in for the literal parent selector text, and + still needs something on both sides of it.

Nesting pseudo-classes without repeating yourself

This is the single most common real-world use case, and it's the one that alone justifies switching. Instead of writing every state of an interactive element as a separate flat rule:

button {
  background: #4338ca;
  color: white;
}

button:hover {
  background: #3730a3;
}

button:focus-visible {
  outline: 2px solid #a5b4fc;
}

button:disabled {
  background: #9ca3af;
  cursor: not-allowed;
}
Enter fullscreen mode Exit fullscreen mode

You keep every state colocated with the base rule it belongs to:

button {
  background: #4338ca;
  color: white;

  &:hover {
    background: #3730a3;
  }

  &:focus-visible {
    outline: 2px solid #a5b4fc;
  }

  &:disabled {
    background: #9ca3af;
    cursor: not-allowed;
  }
}
Enter fullscreen mode Exit fullscreen mode

Functionally identical output. The difference is entirely about where you have to look while editing. When every state a button can be in lives inside one block, you're a lot less likely to update the hover color and forget the focus state sitting three hundred lines further down the file.

Nesting at-rules: media queries, container queries, and more

This is where native nesting pulls ahead of what a lot of people are used to from Sass. You can nest @media, @container, @supports, and @layer directly inside a rule, which keeps responsive and conditional logic sitting right next to the property it actually affects, instead of off in a separate media query block somewhere else in the file:

.sidebar {
  width: 20rem;

  @media (max-width: 768px) {
    width: 100%;
  }

  @container (max-width: 40rem) {
    padding: 0.5rem;
  }
}
Enter fullscreen mode Exit fullscreen mode

Compare that to the traditional approach, where you'd have .sidebar { width: 20rem; } in one place and @media (max-width: 768px) { .sidebar { width: 100%; } } somewhere else entirely, often much further down the stylesheet. Nesting the media query keeps the full story of "what width can this element be" in one spot. For a component with several responsive tweaks, this alone can cut a meaningful amount of back-and-forth scrolling.

Specificity: the good news

Nesting doesn't add any specificity of its own. A nested rule has exactly the same specificity as the equivalent rule written out flat. .card h2 nested inside .card calculates identically to .card h2 written on one line. This is worth knowing because it means nesting is purely a syntax convenience, it doesn't change how the cascade resolves conflicts, and you don't need to relearn specificity rules to use it safely.

That said, nesting makes it very easy to accidentally write something overly specific just by going too many levels deep:

.page {
  .content {
    .card {
      .title {
        color: #111827;
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

That compiles to .page .content .card .title, four selectors deep, which is going to be annoying to override later even though nothing about nesting itself caused the problem, you'd have written an equally gnarly selector by hand if you'd typed it flat. The lesson isn't "nesting is dangerous," it's the same advice that applied before nesting existed: keep selectors as shallow as the actual DOM structure requires, and don't nest just because you can.

The gotcha that catches almost everyone once

Here's the one that's worth reading twice. In the relaxed syntax, a nested rule starting with an identifier that looks like a type selector is fine, h2 { } inside .card is unambiguous. But CSS rules are parsed top to bottom, declarations first, and if a nested selector could be confused with a custom property or declaration, the parser needs the & to disambiguate it.

In practice this mostly comes up with pseudo-elements and certain edge-case selectors, and the safest habit, honestly, is this: when in doubt, just add the &. It's never wrong to include it even where it's technically optional, and a lot of style guides in 2026 recommend always writing it for consistency, precisely so you're not making a judgment call about whether this particular selector needs it every time you write one. I'd rather see & p { } everywhere in a codebase than half the nested rules with & and half without, purely for the sake of not having to think about which category a given selector falls into.

A real component, built with nesting

Here's a small card component pulling everything together, base styles, a modifier, hover and focus states, a sibling gap, and a responsive tweak, all in one block instead of scattered across a stylesheet:

.card {
  display: flex;
  flex-direction: column;
  border: 1px solid #e5e7eb;
  border-radius: 12px;
  padding: 1.25rem;
  background: #ffffff;
  transition: box-shadow 0.2s ease, border-color 0.2s ease;

  &:hover {
    box-shadow: 0 8px 20px rgb(0 0 0 / 0.08);
  }

  &.featured {
    border-color: #6366f1;
  }

  & + & {
    margin-top: 1rem;
  }

  h2 {
    margin: 0 0 0.5rem;
    font-size: 1.15rem;
    color: #111827;
  }

  p {
    margin: 0;
    color: #4b5563;
    line-height: 1.5;
  }

  .tag {
    display: inline-block;
    margin-top: 0.75rem;
    padding: 0.2rem 0.6rem;
    border-radius: 999px;
    background: #eef2ff;
    color: #4338ca;
    font-size: 0.8rem;
    width: fit-content;
  }

  @media (max-width: 480px) {
    padding: 1rem;

    h2 {
      font-size: 1rem;
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Every rule that has anything to do with .card lives inside that one block. Compare that to how this would've looked as six or seven separate flat rules scattered wherever they happened to get added over the life of the file, and it's easy to see why this feature alone gets people to finally drop a Sass build step they've been carrying around for a decade.

If you're migrating from Sass

The syntax is close enough that most Sass nesting will look almost identical once you drop it into native CSS, but there are a few differences worth checking for before you assume a straight copy-paste will work.

Sass lets you nest selectors that don't start with a combinator or an identifier in ways native CSS doesn't allow, and Sass's & supports some string-concatenation tricks, like &-active to produce .button-active, that native CSS doesn't support at all, the native & only works as a full selector, not as a text fragment you can glue other characters onto. Sass also compiles at build time, so it can be more forgiving about ambiguous-looking rules; the browser parses your CSS live, so it applies the stricter rules described above. Run your migrated stylesheet through the browser and actually check computed styles rather than assuming visual parity, particularly on any BEM-style modifier classes built with string concatenation.

Should you drop your preprocessor?

If your only reason for using Sass was nesting, and you're not relying on its other features, real mixins, functions, math operations that go beyond what calc() and clamp() now handle, you can probably drop it for new projects without losing much. For a big existing codebase, it's less about ripping the preprocessor out immediately and more about not reaching for &-modifier string tricks in new code going forward, so the eventual migration is smaller when you do get to it.

Either way, native nesting is worth learning properly rather than treating it as "Sass, but in the browser." The rules are close enough to bite you exactly when you're not paying attention, and different enough that it's worth the twenty minutes to actually understand where they diverge.


We're ArtClick, a web development agency based in Kyoto. We build company websites, WordPress sites, and custom systems — with a focus on sites that are fast, well-designed, and easy to maintain long-term. Learn more at artclickdev.

Top comments (0)