tailwindlabs/tailwindcss · error · Error

Cannot use `@variant` with empty variant

Error message

Cannot use `@variant` with empty variant

What it means

Thrown by substituteAtVariant() while processing an @variant at-rule in CSS. The rule's params are split on ':' to get a stacked variant list (e.g. 'hover:focus'); if any segment is empty after trimming, the variant is malformed. This catches stray colons like '@variant hover:' or '@variant :focus'.

Source

Thrown at packages/tailwindcss/src/variants.ts:1300

    for (let [idx, compoundVariant] of compoundVariants.entries()) {
      // Starting with the `&` rule node
      //
      // Only clone the nodes when we have multiple compound variants to deal
      // with. The last one can use the original nodes. We do need unique AST
      // nodes for sourcemap `dst` location information.
      let node = styleRule(
        '&',
        idx === compoundVariants.length - 1
          ? variantNode.nodes
          : variantNode.nodes.map(cloneAstNode),
      )

      let stackedVariants = segment(compoundVariant, ':')
      for (let i = stackedVariants.length - 1; i >= 0; --i) {
        let variant = stackedVariants[i].trim()

        if (!variant) {
          throw new Error(`Cannot use \`@variant\` with empty variant`)
        }

        let variantAst = designSystem.parseVariant(variant)
        if (variantAst === null) {
          throw new Error(`Cannot use \`@variant\` with unknown variant: ${variant}`)
        }

        let result = applyVariant(node, variantAst, designSystem.variants)
        if (result === null) {
          throw new Error(`Cannot use \`@variant\` with variant: ${variant}`)
        }
      }

      if (node.selector === '&') {
        nodes.push(...node.nodes)
      } else {
        nodes.push(node)
      }

View on GitHub (pinned to 16e94cbf7f)

Solutions

  1. Remove the stray colon so every segment is non-empty: '@variant hover:focus { ... }'.
  2. When building variant stacks dynamically, filter out empty segments before joining.
  3. Validate that no segment of the colon-split string is empty before writing the @variant rule.

Example fix

/* before — throws */
@variant :hover {
  .btn { color: red; }
}

/* after */
@variant hover {
  .btn { color: red; }
}
Defensive patterns

Strategy: validation

Validate before calling

function validateVariantStack(params: string): void {
  for (const part of params.split(':')) {
    if (part.trim() === '') {
      throw new Error(`Empty variant segment in '${params}'`)
    }
  }
}

Type guard

function hasNoEmptyVariant(params: string): boolean {
  return params.split(':').every((s) => s.trim().length > 0)
}

Prevention

When it happens

Trigger: Writing `@variant :focus { ... }` (leading colon), `@variant hover: { ... }` (trailing colon), or `@variant hover::focus { ... }` (double colon) in CSS processed by Tailwind v4. The segment() split produces an empty string for the slot between/around the colons.

Common situations: Manually authoring @variant rules and adding a stray colon. Programmatically building variant strings by joining with ':' when one element is empty. Copying pseudo-selector syntax (::) into a variant stack.

Related errors


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