{"record":{"id":"b381f49393e6c0e6","repo":"tailwindlabs/tailwindcss","slug":"cannot-use-variant-with-empty-variant","errorCode":null,"errorMessage":"Cannot use `@variant` with empty variant","messagePattern":"Cannot use `@variant` with empty variant","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/tailwindcss/src/variants.ts","lineNumber":1300,"sourceCode":"    for (let [idx, compoundVariant] of compoundVariants.entries()) {\n      // Starting with the `&` rule node\n      //\n      // Only clone the nodes when we have multiple compound variants to deal\n      // with. The last one can use the original nodes. We do need unique AST\n      // nodes for sourcemap `dst` location information.\n      let node = styleRule(\n        '&',\n        idx === compoundVariants.length - 1\n          ? variantNode.nodes\n          : variantNode.nodes.map(cloneAstNode),\n      )\n\n      let stackedVariants = segment(compoundVariant, ':')\n      for (let i = stackedVariants.length - 1; i >= 0; --i) {\n        let variant = stackedVariants[i].trim()\n\n        if (!variant) {\n          throw new Error(`Cannot use \\`@variant\\` with empty variant`)\n        }\n\n        let variantAst = designSystem.parseVariant(variant)\n        if (variantAst === null) {\n          throw new Error(`Cannot use \\`@variant\\` with unknown variant: ${variant}`)\n        }\n\n        let result = applyVariant(node, variantAst, designSystem.variants)\n        if (result === null) {\n          throw new Error(`Cannot use \\`@variant\\` with variant: ${variant}`)\n        }\n      }\n\n      if (node.selector === '&') {\n        nodes.push(...node.nodes)\n      } else {\n        nodes.push(node)\n      }","sourceCodeStart":1282,"sourceCodeEnd":1318,"githubUrl":"https://github.com/tailwindlabs/tailwindcss/blob/16e94cbf7f965c5ad697e90e940b5e178efad67c/packages/tailwindcss/src/variants.ts#L1282-L1318","documentation":"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'.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Remove the stray colon so every segment is non-empty: '@variant hover:focus { ... }'.","When building variant stacks dynamically, filter out empty segments before joining.","Validate that no segment of the colon-split string is empty before writing the @variant rule."],"exampleFix":"/* before — throws */\n@variant :hover {\n  .btn { color: red; }\n}\n\n/* after */\n@variant hover {\n  .btn { color: red; }\n}","handlingStrategy":"validation","validationCode":"function validateVariantStack(params: string): void {\n  for (const part of params.split(':')) {\n    if (part.trim() === '') {\n      throw new Error(`Empty variant segment in '${params}'`)\n    }\n  }\n}","typeGuard":"function hasNoEmptyVariant(params: string): boolean {\n  return params.split(':').every((s) => s.trim().length > 0)\n}","tryCatchPattern":null,"preventionTips":["Avoid leading/trailing/double colons in @variant params.","When joining variant stacks dynamically, filter empty strings first.","Lint @variant rules for stray colons."],"tags":["tailwindcss","variant","css","v4","at-rule"],"backgroundTag":null,"analyzedSha":"16e94cbf7f965c5ad697e90e940b5e178efad67c","analyzedAt":"2026-08-12T06:02:42.469Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}