EmpireUI
Get Pro
← Blog8 min read#css-nesting#css#react

Native CSS Nesting: Full Guide with Real Component Examples

Native CSS nesting is finally here — no preprocessor needed. Learn how to use it in real React components, with Tailwind, and avoid the quirks that'll trip you up.

Code editor showing CSS code with colorful syntax highlighting on a dark background

Native CSS Nesting Is Here — And It's Not Quite What You Expect

Honestly, native CSS nesting looked like a done deal two years ago, and then browser support crawled across the finish line in ways that made it awkward to actually use. But as of Chrome 120, Firefox 117, and Safari 17.2, you can write nested CSS in production with zero preprocessors and no build step.

That doesn't mean you should throw out Sass or stop using Tailwind v4.0.2. It means you have a real choice now. And making that choice well requires understanding what native nesting actually does — and where it still surprises you.

This guide walks through the spec, the browser behavior, real component examples you'd actually write in a React project, and the edge cases worth knowing before you commit to it.

How the CSS Nesting Syntax Actually Works

The basic idea is the same as Sass: you write rules inside other rules, and the browser resolves the parent-child relationship for you. But there's a critical difference. In native CSS, any nested rule that starts with a letter (not &, ., #, >, ~, +, or :) requires you to prepend & or wrap it in :is().

This trips people up constantly. Writing p { color: red; } inside a .card rule used to silently fail in older implementations. Modern browsers handle it via the :is() fallback internally, but if you're targeting Chrome 112 or earlier, you need the & prefix explicitly.

Here's a baseline example of what valid native nesting looks like:

.card {
  background: rgba(255,255,255,0.08);
  border-radius: 12px;
  padding: 24px;

  /* Direct child element — fine, starts with & */
  & .card__title {
    font-size: 1.25rem;
    font-weight: 600;
  }

  /* Pseudo-class nesting */
  &:hover {
    background: rgba(255,255,255,0.15);
    transform: translateY(-2px);
  }

  /* Media query inside the rule — this is new */
  @media (max-width: 640px) {
    padding: 16px;
  }
}

Notice the @media block is nested directly inside .card. That's one of the genuinely useful things native nesting brings: you can scope your responsive overrides right where the component styles live, not at the bottom of a 400-line file.

Using Native Nesting Inside React Component Stylesheets

If you're using CSS Modules with React, native nesting slots in with zero extra config. Vite 5.x and Next.js 14+ both pass your .module.css files through PostCSS, which already supports nesting via postcss-nesting. As of PostCSS 8.4.31, you can also enable the native syntax directly without the plugin if you're targeting only modern browsers.

Here's a real button component that uses nesting for variants and states:

// Button.tsx
import styles from './Button.module.css';

type ButtonProps = {
  variant?: 'primary' | 'ghost';
  size?: 'sm' | 'md';
  children: React.ReactNode;
};

export function Button({ variant = 'primary', size = 'md', children }: ButtonProps) {
  return (
    <button
      className={[
        styles.btn,
        styles[`btn--${variant}`],
        styles[`btn--${size}`],
      ].join(' ')}
    >
      {children}
    </button>
  );
}
```

```css
/* Button.module.css */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  border-radius: 8px;
  font-weight: 500;
  cursor: pointer;
  transition: all 0.15s ease;
  border: none;

  &:focus-visible {
    outline: 2px solid #6366f1;
    outline-offset: 2px;
  }

  &:disabled {
    opacity: 0.45;
    cursor: not-allowed;
  }

  &.btn--primary {
    background: #6366f1;
    color: #fff;

    &:hover:not(:disabled) {
      background: #4f46e5;
    }
  }

  &.btn--ghost {
    background: transparent;
    color: #6366f1;
    border: 1px solid rgba(99,102,241,0.4);

    &:hover:not(:disabled) {
      background: rgba(99,102,241,0.08);
    }
  }

  &.btn--sm { padding: 6px 12px; font-size: 0.875rem; }
  &.btn--md { padding: 10px 20px; font-size: 1rem; }
}

The double-nesting on &.btn--primary > &:hover:not(:disabled) is exactly the kind of thing that would have required BEM gymnastics or two separate rule blocks before. Now it reads like the structure it describes.

Where Native Nesting Differs from Sass (Don't Assume Parity)

Sass has had nesting for over a decade, so there are habits baked in that don't transfer. The most common one: Sass lets you nest any property block inside another selector, including bare element selectors, without &. Native CSS doesn't work that way — or rather, it didn't until the spec updated to add implicit & behavior for element selectors, which Chrome shipped in v120 but Firefox handled differently until v117.2.

Another difference is string interpolation. You can't do &-modifier in native CSS the way Sass lets you build selector strings dynamically. &.modifier (with a dot) works fine, but &__element to produce .parent__element doesn't — you'd still need Sass or a PostCSS plugin for that BEM pattern.

Can you mix native nesting with Tailwind? Yes, but it depends on your setup. If you're writing a @layer components block inside a CSS file with Tailwind v4.0.2, nesting inside those layer blocks works as expected. The Tailwind v4 engine uses Lightning CSS under the hood, which supports nesting natively.

Nesting @media and @container Queries In-Component

This is the feature that changes how you write responsive CSS more than anything else. Instead of hunting for the right @media block at the end of your stylesheet, you drop it directly inside the component rule. The browser resolves it correctly in all modern engines.

Container queries work the same way. If you're building components that need to respond to their parent container rather than the viewport — which you should be for reusable UI — nesting @container inside your rule keeps everything readable. See how this connects to building truly isolated UI; if you're coming from an approach like CSS Modules vs. Tailwind, the co-location pattern here feels familiar.

.stat-card {
  container-type: inline-size;
  display: grid;
  grid-template-columns: 1fr;
  gap: 8px;
  padding: 20px;

  @container (min-width: 280px) {
    grid-template-columns: auto 1fr;
    gap: 16px;
  }

  @media (prefers-color-scheme: dark) {
    background: rgba(255,255,255,0.05);
    color: #f1f5f9;
  }

  & .stat-card__value {
    font-size: clamp(1.5rem, 4cqi, 2.5rem);
    font-weight: 700;
  }
}

The cqi unit inside clamp() there — that's container query inline-axis units, available in Chrome 105+. Nesting your @container rule in-component makes it obvious that the font scaling is tied to that specific container, not a global viewport rule you'd have to track down later.

Nesting and Specificity: The Part That'll Bite You

Here's the thing: nesting doesn't change specificity calculations. & .card__title inside .card produces the same specificity as .card .card__title written flat. That's expected. What catches people is when they nest :is() implicitly — some browsers internally wrap nested element selectors in :is(), which can zero out specificity for the tag part.

For example, p inside .card in older implementations resolved as :is(p) inside .card, giving it the specificity of .card :is(p) — which treats the p part as having zero tag-level specificity. In practice this matters when you're overriding base typography styles. If your reset has p { margin: 0; } and you write nested p { margin-bottom: 1rem; } inside .prose, the specificity might not win the way you expect.

The fix is explicit: use & p rather than bare p. It produces .prose p in specificity terms, which wins over a flat p reset. This is also why relying on the implicit behavior is risky for cross-browser work right now. Be explicit. Write the &.

Native Nesting vs. Tailwind: When to Use Which

If you're already on Tailwind, you're probably asking whether you'd ever reach for native nesting at all. The answer is yes — for stateful and pseudo-class logic that doesn't fit cleanly into utility classes, and for anything involving @container queries at the component level.

Tailwind's group and peer utilities handle some parent-state scenarios, but they add extra markup and class management. Sometimes &:hover .inner-element { opacity: 1; } is just cleaner than wrapping everything in a group div and scattering group-hover: prefixes across your JSX. For glassmorphism-style components — the kind you'd read about in what is glassmorphism — the backdrop-filter and nested pseudo-element styles are often easier to write in a CSS file with nesting than inline Tailwind.

The two approaches aren't enemies. A lot of production React code uses Tailwind for layout and spacing, then drops into a .module.css with nesting for component-specific interactive states. Tailwind v4.0.2 is good at the grid. Native nesting is good at the fine-grained behavior. Use both.

Browser Support Reality Check and Progressive Enhancement

As of late 2026, global support for native CSS nesting sits at about 93% across all browsers. The remaining 7% is mostly older Android WebView instances and enterprise IE-era holdovers. For most SaaS products and developer tools, you can ship native nesting without a PostCSS fallback today.

If you need broader coverage — ecommerce with older mobile devices, for instance — postcss-nesting (v13.x) transforms your native nesting syntax at build time into flat selectors that work everywhere. Your source code stays readable; the output is compatible. You don't have to pick between authoring experience and support.

Worth knowing: if you're building animated or canvas-heavy UIs where CSS is only part of the story — like CSS Houdini paint worklets or WebGL backgrounds — native nesting is completely orthogonal to those techniques. You can nest your CSS animation keyframe trigger rules inside component selectors just like any other property. No conflicts.

The spec is stable. The support is there. Write nesting in your CSS without guilt — just be explicit with your & and you'll avoid the 95% of edge cases that catch people out.

FAQ

Do I need a PostCSS plugin to use native CSS nesting in 2026?

Not for modern browsers. Chrome 120+, Firefox 117+, and Safari 17.2+ support it natively. If you're using Vite 5+ or Next.js 14+, PostCSS is already in the pipeline and handles nesting automatically. Only add postcss-nesting explicitly if you need to support older Android WebView or specific enterprise environments.

Why doesn't `&-modifier` work in native CSS nesting like it does in Sass?

Native CSS nesting doesn't support string concatenation via &. The & token represents the full parent selector as-is — you can't append characters to it to build new selector names. &.modifier (dot-separated) works, but &__element to produce .block__element doesn't. For BEM-style concatenation, you still need Sass or a PostCSS plugin.

Can I nest @media and @container queries inside a CSS rule?

Yes, and it's one of the best features. Both @media and @container can be nested directly inside a selector rule. The browser resolves them correctly. This lets you keep responsive and container-based overrides co-located with the component styles they affect, rather than scattered at the bottom of your stylesheet.

Does nesting change CSS specificity?

No. & .child inside .parent produces the same specificity as .parent .child written flat. The one edge case to watch: bare element selectors (like p without &) in older browser implementations were wrapped in :is(), which drops the tag-level specificity of the element. Use & p instead of bare p to be safe and explicit.

Does native CSS nesting work inside Tailwind @layer blocks?

Yes. Tailwind v4.0.2 uses Lightning CSS as its internal engine, which supports CSS nesting natively. If you're writing custom components inside @layer components, you can nest selectors, pseudo-classes, and media queries inside those blocks without any extra config.

Is it safe to ship native CSS nesting in production today?

For most developer tools, SaaS products, and agency sites targeting modern browsers — yes. Browser support is around 93% globally as of late 2026. If your analytics show significant traffic from older Android devices or legacy enterprise browsers, add postcss-nesting as a build-time transform. Your source code stays the same; only the output changes.

Free components in 40 styles
React & Tailwind, copy-paste ready.
Browse →

Read next

Container Style Queries: CSS Theming Without JavaScriptCSS Subgrid in Production: Aligning Children Across Rows and ColsTailwind CSS Mastery: Every Utility, Plugin, and Pattern in One GuideThe Ultimate CSS UI Styles Guide: All 40 Visual Styles Ranked (2026)