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
- Move the `@custom-variant` rule to the top level of the stylesheet.
- If you need conditional variant behavior, apply variants compositionally at the call site instead of nesting the definition.
- 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
- Define all `@custom-variant` rules once in your CSS entry file at the top level.
- Apply variants compositionally at the utility call site rather than nesting definitions.
- Lint for non-root parents of `@custom-variant`.
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
- `@utility` cannot be nested.
- `@source` cannot be nested.
- `@custom-variant ${name}` cannot have both a selector and a
- `@plugin` cannot be nested.
- `@config` cannot be nested.
AI-assisted analysis of tailwindlabs/tailwindcss@16e94cbf7f (2026-08-12).
Data as JSON: /api/errors/20433aef5ac26faf.
Report an issue: GitHub.