{"record":{"id":"bd7e813aeb2cf87b","repo":"tailwindlabs/tailwindcss","slug":"addvariant-name-defines-an-invalid-variant","errorCode":null,"errorMessage":"`addVariant('${name}')` defines an invalid variant name. Variants should only contain alphanumeric, dashes, or underscore characters and start with a lowercase letter or number.","messagePattern":"`addVariant\\('(.+?)'\\)` defines an invalid variant name\\. Variants should only contain alphanumeric, dashes, or underscore characters and start with a lowercase letter or number\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/tailwindcss/src/compat/plugin-api.ts","lineNumber":122,"sourceCode":"  featuresRef: { current: Features }\n  referenceMode: boolean\n  src: SourceLocation | undefined\n}): PluginAPI {\n  let api: PluginAPI = {\n    addBase(css) {\n      if (referenceMode) return\n      let baseNodes = objectToAst(css)\n      featuresRef.current |= substituteFunctions(baseNodes, designSystem)\n      let rule = atRule('@layer', 'base', baseNodes)\n      walk([rule], (node) => {\n        node.src = src\n      })\n      ast.push(rule)\n    },\n\n    addVariant(name, variant) {\n      if (!IS_VALID_VARIANT_NAME.test(name)) {\n        throw new Error(\n          `\\`addVariant('${name}')\\` defines an invalid variant name. Variants should only contain alphanumeric, dashes, or underscore characters and start with a lowercase letter or number.`,\n        )\n      }\n\n      // Ignore variants emitting v3 `:merge(…)` rules. In v4, the `group-*` and `peer-*` variants\n      // compound automatically.\n      if (typeof variant === 'string') {\n        if (variant.includes(':merge(')) return\n      } else if (Array.isArray(variant)) {\n        if (variant.some((v) => v.includes(':merge('))) return\n      } else if (typeof variant === 'object') {\n        function keyIncludes(object: Record<string, any>, search: string): boolean {\n          return Object.entries(object).some(\n            ([key, value]) =>\n              key.includes(search) || (typeof value === 'object' && keyIncludes(value, search)),\n          )\n        }\n        if (keyIncludes(variant, ':merge(')) return","sourceCodeStart":104,"sourceCodeEnd":140,"githubUrl":"https://github.com/tailwindlabs/tailwindcss/blob/16e94cbf7f965c5ad697e90e940b5e178efad67c/packages/tailwindcss/src/compat/plugin-api.ts#L104-L140","documentation":"Thrown by the plugin API's `addVariant(name, ...)` when `name` fails `IS_VALID_VARIANT_NAME = /^@?[a-z0-9][a-zA-Z0-9_-]*(?<![_-])$/`. Variant names must start with a lowercase letter or digit, contain only alphanumeric/underscore/dash, and must not end with `_` or `-`. The optional leading `@` is allowed.","triggerScenarios":"Calling `addVariant('Hover', ...)`, `addVariant('2xl-modal', ...)` is fine, but `addVariant('_foo', ...)`, `addVariant('Foo', ...)`, `addVariant('foo!', ...)`, or `addVariant('foo-', ...)` throws. The regex is applied at registration time inside the plugin handler.","commonSituations":"Authoring a custom plugin with a PascalCase or symbol-containing variant name; copy-pasting a variant name from a selector; trailing dash from string concatenation.","solutions":["Rename the variant to match the regex: start `[a-z0-9]`, body `[a-zA-Z0-9_-]*`, no trailing `_` or `-` (e.g. `hover-modal`, `md_2`).","Strip a leading `@` only if you intentionally include it; otherwise omit it.","Validate the name with the same regex in your plugin tests before release."],"exampleFix":"// before\nplugin(function ({ addVariant }) {\n  addVariant('Hover-Focus-', '&:hover, &:focus')\n})\n// after\nplugin(function ({ addVariant }) {\n  addVariant('hover-focus', '&:hover, &:focus')\n})","handlingStrategy":"validation","validationCode":"const IS_VALID_VARIANT_NAME = /^@?[a-z0-9][a-zA-Z0-9_-]*(?<![_-])$/;\nfunction assertValidVariantName(name) {\n  if (!IS_VALID_VARIANT_NAME.test(name)) {\n    throw new Error(`Invalid variant name: ${name}`);\n  }\n}\n// assertValidVariantName('hover-focus');","typeGuard":"function isValidVariantName(name: string): boolean {\n  return /^@?[a-z0-9][a-zA-Z0-9_-]*(?<![_-])$/.test(name);\n}","tryCatchPattern":null,"preventionTips":["Unit-test `addVariant` names against the regex in plugin test suites.","Restrict variant naming conventions in a shared style guide for plugin authors."],"tags":["tailwind","plugin","variant","validation"],"backgroundTag":null,"analyzedSha":"16e94cbf7f965c5ad697e90e940b5e178efad67c","analyzedAt":"2026-08-12T06:02:42.469Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}