tailwindlabs/tailwindcss · error · Error
`@custom-variant ${name}` cannot have both a selector and a
Error message
`@custom-variant ${name}` cannot have both a selector and a body. What it means
Thrown when a `@custom-variant` provides both a parenthesized selector in its prelude AND a curly-brace body. Tailwind treats the variant definition as either selector-form (`@custom-variant hocus (&:hover, &:focus);`) or body-form (`@custom-variant hocus { ... }`), not both.
Source
Thrown at packages/tailwindcss/src/index.ts:366
}
}
// 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.`)
}
let selectors = segment(selector.slice(1, -1), ',')
if (selectors.length === 0 || selectors.some((selector) => selector.trim() === '')) {
throw new Error(
`\`@custom-variant ${name} (${selectors.join(',')})\` selector is invalid.`,
)
}
let atRuleParams: string[] = []
let styleRuleSelectors: string[] = []
View on GitHub (pinned to 16e94cbf7f)
Solutions
- Use only the selector form: `@custom-variant hocus (&:hover, &:focus);` (no body).
- Or use only the body form: `@custom-variant hocus { &:hover, &:focus { @variant ...; } }` (no selector in prelude).
- Pick the form that matches your intent: selector-form for simple selector composition, body-form for referencing other variants via `@variant`.
Example fix
/* before */
@custom-variant hocus (&:hover, &:focus) {
/* rules */
}
/* after — selector form */
@custom-variant hocus (&:hover, &:focus); Defensive patterns
Strategy: validation
Validate before calling
// Ensure @custom-variant has at most one of (selector, body).
import postcss from 'postcss'
function validateCustomVariantForm(css: string): string[] {
const errors: string[] = []
postcss.parse(css).walkAtRules('@custom-variant', (rule) => {
const hasBody = rule.nodes.length > 0
const hasSelector = rule.params.split(' ').length > 1
if (hasBody && hasSelector) {
errors.push(`@custom-variant ${rule.params} has both selector and body`)
}
})
return errors
} Prevention
- Pick one form per variant: selector-form (`name (...)`) or body-form (`name { ... }`).
- In code review, flag any `@custom-variant` line that has both a `(...)` prelude and a `{ ... }` block.
- Document the two forms in your project's CSS conventions.
When it happens
Trigger: Writing `@custom-variant hocus (&:hover, &:focus) { color: red; }` — selector in prelude and a non-empty body.
Common situations: Adding a body while forgetting to remove the selector argument; misunderstanding the two syntactic forms.
Related errors
- `@source` cannot have a body.
- `@custom-variant` cannot be nested.
- `@custom-variant ${name}` defines an invalid variant name. V
- `@custom-variant ${name}` has no selector or body.
- `@custom-variant ${name} (${selectors.join(',')})` selector
AI-assisted analysis of tailwindlabs/tailwindcss@16e94cbf7f (2026-08-12).
Data as JSON: /api/errors/9241d8e1858b6c1d.
Report an issue: GitHub.