tailwindlabs/tailwindcss · error · Error

`@custom-variant ${name}` has no selector or body.

Error message

`@custom-variant ${name}` has no selector or body.

What it means

Thrown when a `@custom-variant` has neither a body (`node.nodes.length === 0`) nor a selector token in its prelude. A custom variant must define at least one of the two, otherwise there is nothing to register.

Source

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

        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[] = []

        for (let selector of selectors) {
          selector = selector.trim()

          if (selector[0] === '@') {
            atRuleParams.push(selector)
          } else {

View on GitHub (pinned to 16e94cbf7f)

Solutions

  1. Add a selector: `@custom-variant hocus (&:hover, &:focus);`.
  2. Or add a body: `@custom-variant hocus { &:hover { @variant ...; } }`.
  3. If the variant is unused, remove the `@custom-variant` rule entirely.

Example fix

/* before */
@custom-variant hocus;

/* after */
@custom-variant hocus (&:hover, &:focus);
Defensive patterns

Strategy: validation

Validate before calling

// Ensure @custom-variant has a selector or a body.
import postcss from 'postcss'

function validateCustomVariantHasContent(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 no selector or body`)
    }
  })
  return errors
}

Prevention

When it happens

Trigger: Writing `@custom-variant hocus;` with no arguments and no body.

Common situations: Stubbing a variant name for later; typo that dropped the selector; refactoring that removed the body.

Related errors


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