tailwindlabs/tailwindcss · error · Error

`@custom-variant ${name} (${selectors.join(',')})` selector

Error message

`@custom-variant ${name} (${selectors.join(',')})` selector is invalid.

What it means

Thrown in selector-form when the parenthesized selector, after stripping the outer parens and splitting on `,`, is empty or contains empty segments. The check `selectors.length === 0 || selectors.some(s => s.trim() === '')` rejects inputs like `()` or `(,)` that would produce no real selectors.

Source

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

      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 {
            styleRuleSelectors.push(selector)
          }
        }

        let usesAtScope = atRuleParams.some((param) => param.startsWith('@scope'))

View on GitHub (pinned to 16e94cbf7f)

Solutions

  1. Provide at least one non-empty selector: `@custom-variant hocus (&:hover, &:focus)`.
  2. Remove stray commas or whitespace inside the parens.
  3. Switch to the body form if the variant logic is more complex than a selector list.

Example fix

/* before */
@custom-variant hocus (,);

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

Strategy: validation

Validate before calling

// Validate the selector list inside @custom-variant name (...).
function validateVariantSelectorList(selectorGroup: string): string | null {
  // selectorGroup is the parenthesized portion, e.g. "(&:hover, &:focus)"
  if (!selectorGroup.startsWith('(') || !selectorGroup.endsWith(')')) {
    return null // not selector-form; body validation handles it
  }
  const inner = selectorGroup.slice(1, -1)
  const parts = inner.split(',')
  if (parts.length === 0 || parts.some((s) => s.trim() === '')) {
    return `Selector list '${selectorGroup}' is empty or contains empty entries`
  }
  return null
}

Prevention

When it happens

Trigger: Writing `@custom-variant hocus ()` or `@custom-variant hocus (,)` or `@custom-variant hocus ( , )`.

Common situations: Leaving the parens empty while editing; trailing comma from a deleted selector; whitespace-only entries.

Related errors


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