tailwindlabs/tailwindcss · error · Error

`@custom-variant` cannot be nested.

Error message

`@custom-variant` cannot be nested.

What it means

Thrown when a `@custom-variant` at-rule is encountered with a non-null parent (`ctx.parent !== null`). Custom variants must be defined at the stylesheet's top level so they can be registered globally before any `@variant` references are resolved.

Source

Thrown at packages/tailwindcss/src/index.ts:354

          // No `@slot` found, so this is still a regular `@variant` at-rule
          if (node.name === '@variant') {
            variantNodes.push(node)
          }
        }
      }

      // Collect all the `@variant` at-rules, we will replace them later once
      // all variants are registered in the system.
      else {
        variantNodes.push(node)
      }
    }

    // Register custom variants from `@custom-variant` at-rules
    if (node.name === '@custom-variant') {
      if (ctx.parent !== null) {
        throw new Error('`@custom-variant` cannot be nested.')
      }

      let [name, selector] = segment(node.params, ' ')

      if (!IS_VALID_VARIANT_NAME.test(name)) {
        throw new Error(
          `\`@custom-variant ${name}\` defines an invalid variant name. Variants should only contain alphanumeric, dashes, or underscore characters and start with a lowercase letter or number.`,
        )
      }

      if (node.nodes.length > 0 && selector) {
        throw new Error(`\`@custom-variant ${name}\` cannot have both a selector and a body.`)
      }

      // Variants with a selector, but without a body, e.g.: `@custom-variant hocus (&:hover, &:focus);`
      if (node.nodes.length === 0) {
        if (!selector) {
          throw new Error(`\`@custom-variant ${name}\` has no selector or body.`)

View on GitHub (pinned to 16e94cbf7f)

Solutions

  1. Move the `@custom-variant` rule to the top level of the stylesheet.
  2. If you need conditional variant behavior, apply variants compositionally at the call site instead of nesting the definition.
  3. Define the variant in your main CSS entry file rather than inside component-scoped CSS.

Example fix

/* before */
@media (prefers-color-scheme: dark) {
  @custom-variant dark (&:where(.dark *));
}

/* after */
@custom-variant dark (&:where(.dark *));
/* then use: <div class="dark:bg-black"> */
Defensive patterns

Strategy: validation

Validate before calling

// Reject nested @custom-variant.
import postcss from 'postcss'

function validateCustomVariantTopLevel(css: string): string[] {
  const errors: string[] = []
  postcss.parse(css).walkAtRules('@custom-variant', (rule) => {
    if (rule.parent && rule.parent.type !== 'root') {
      errors.push(`@custom-variant ${rule.params} must be at the top level`)
    }
  })
  return errors
}

Prevention

When it happens

Trigger: Placing `@custom-variant hocus (&:hover, &:focus)` inside `@media`, a selector, or any other rule.

Common situations: Trying to scope a variant to a subtree; refactors that indent directive blocks; copy-paste into an existing nested context.

Related errors


AI-assisted analysis of tailwindlabs/tailwindcss@16e94cbf7f (2026-08-12). Data as JSON: /api/errors/20433aef5ac26faf. Report an issue: GitHub.