Skip to content

Fix Tailwind v4 custom-variant nesting error

fix

Tailwind v4 build fails with CssSyntaxError when @custom-variant is nested inside another rule

tailwindcsstailwind-v4migration
23 views

Problem

After migrating to Tailwind v4, the build fails with a CssSyntaxError when a @custom-variant declaration is nested inside @layer or another at-rule:

CssSyntaxError: tailwindcss: @custom-variant cannot be nested.

  > 1 | @layer utilities {
    2 |   @custom-variant pointer-coarse (@media (pointer: coarse));
      |   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    3 | }

  at /app/src/styles/globals.css:2:3

The broken stylesheet that triggers the error:

@import "tailwindcss";

@layer utilities {
  @custom-variant pointer-coarse (@media (pointer: coarse));
  @custom-variant dark-mode (&:where(.dark, .dark *));
}

Solution

Move @custom-variant declarations to the root level of the stylesheet, outside any @layer or nested block:

@import "tailwindcss";

@custom-variant pointer-coarse (@media (pointer: coarse));
@custom-variant dark-mode (&:where(.dark, .dark *));

If you have multiple custom variants mixed with utility definitions, separate them:

@import "tailwindcss";

/* Custom variants must be at root level */
@custom-variant pointer-coarse (@media (pointer: coarse));
@custom-variant dark-mode (&:where(.dark, .dark *));

/* Utilities can remain inside @layer or @utility */
@utility container-narrow {
  max-width: 48rem;
  margin-inline: auto;
}

Why It Works

Tailwind v4 resolves variant definitions during an early pass of the CSS parser, before @layer ordering and rule nesting are processed. The @custom-variant directive must be visible at the top level so the engine can register it as a variant before any utility classes referencing it are generated. Nesting it inside @layer or another at-rule puts it in a scope the variant resolver cannot reach, causing the parse failure.

Context

  • Tailwind CSS v4.0+ with the new CSS-first configuration model
  • In Tailwind v3, custom variants were defined in tailwind.config.js via addVariant() -- there was no CSS-level equivalent to nest incorrectly
  • The same top-level requirement applies to @theme and @source directives in v4
  • If using PostCSS with @tailwindcss/postcss, the error surfaces during the PostCSS transform step
  • The @tailwindcss/upgrade codemod handles most migrations but may miss custom variants that were manually added to CSS files after initial setup
  • This also applies to @custom-variant with block syntax: @custom-variant dark-mode { &:where(.dark, .dark *) { @slot; } }
About this share
Contributormblode
Repositorymblode/shares
CreatedFeb 9, 2026
Environmenttailwind 4.0+
View on GitHub