{"record":{"id":"547b9147036f77af","repo":"tailwindlabs/tailwindcss","slug":"addutilities-name-defines-an-inval","errorCode":null,"errorMessage":"`addUtilities({ '${name}' : … })` defines an invalid utility selector. Utilities must be a single class name and start with a lowercase letter, eg. `.scrollbar-none`.","messagePattern":"`addUtilities\\((.+?)\\)` defines an invalid utility selector\\. Utilities must be a single class name and start with a lowercase letter, eg\\. `\\.scrollbar-none`\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/tailwindcss/src/compat/plugin-api.ts","lineNumber":326,"sourceCode":"\n            node.value = value\n            return\n          }\n\n          if (\n            node.kind === 'function' &&\n            (node.value === ':not' ||\n              // A class inside `:nth-child(… of <selector>)` is part of the\n              // condition, not a utility being defined.\n              node.value === ':nth-child' ||\n              node.value === ':nth-last-child')\n          ) {\n            return WalkAction.Skip\n          }\n        })\n\n        if (!foundValidUtility) {\n          throw new Error(\n            `\\`addUtilities({ '${name}' : … })\\` defines an invalid utility selector. Utilities must be a single class name and start with a lowercase letter, eg. \\`.scrollbar-none\\`.`,\n          )\n        }\n      }\n\n      for (let [className, ast] of utils) {\n        // Prefix all class selector with the configured theme prefix\n        if (designSystem.theme.prefix) {\n          walk(ast, (node) => {\n            if (node.kind === 'rule') {\n              let selectorAst = SelectorParser.parse(node.selector)\n              walk(selectorAst, (node) => {\n                if (node.kind === 'selector' && node.value[0] === '.') {\n                  node.value = `.${designSystem.theme.prefix}\\\\:${node.value.slice(1)}`\n                }\n              })\n              node.selector = SelectorParser.toCss(selectorAst)\n            }","sourceCodeStart":308,"sourceCodeEnd":344,"githubUrl":"https://github.com/tailwindlabs/tailwindcss/blob/16e94cbf7f965c5ad697e90e940b5e178efad67c/packages/tailwindcss/src/compat/plugin-api.ts#L308-L344","documentation":"Thrown by the v4 compatibility layer's `addUtilities` API when every selector passed to it lacks a valid utility class. A valid utility is a single class whose name (after the leading dot) matches `/^[a-z@][a-zA-Z0-9/%._-]*$/` — it must start with a lowercase letter (or @). The walker also intentionally skips classes nested inside `:not(...)`, `:nth-child(... of ...)`, and `:nth-last-child(...)` because those are conditions, not the utility being defined. If no qualifying class is found anywhere in the selector, the call is rejected outright because Tailwind cannot turn it into a registered candidate.","triggerScenarios":"A legacy JS plugin calls `addUtilities({ '.FOO': {...} })` (uppercase first letter), `addUtilities({ 'div': {...} })` (element selector with no class), `addUtilities({ '.a:hover': {...} })` where the only class is consumed inside a pseudo, or `addUtilities({ '.123abc': {...} })` (leading digit). Also triggered by selectors where the sole class lives inside `:not(.x)` so it is skipped by the WalkAction.Skip branch.","commonSituations":"Porting a v3 plugin to v4 that used complex or compound selectors; third-party plugins that emitted element/attribute selectors; authoring a custom plugin with a typo in the class name (e.g. leading capital or leading digit); a plugin that defined a utility purely as `&[data-x]` with no class.","solutions":["Rewrite the selector so it contains exactly one class starting with a lowercase ASCII letter, e.g. `.scrollbar-none`, matching `/^[a-z@][a-zA-Z0-9/%._-]*$/`.","If you need a compound or descendant selector, anchor it on the utility class, e.g. `.my-util > .child` or `.my-util:hover` — the class itself must still be present and valid.","Move purely structural CSS that has no class (element/attribute selectors) into `addBase` instead of `addUtilities`, since `addBase` does not enforce the utility-class rule.","Check the offending plugin name printed in the surrounding stack and upgrade it to a v4-compatible release."],"exampleFix":"// before\nplugin(function ({ addUtilities }) {\n  addUtilities({\n    '.Scrollbar-none': { scrollbarWidth: 'none' },\n  })\n})\n// after\nplugin(function ({ addUtilities }) {\n  addUtilities({\n    '.scrollbar-none': { scrollbarWidth: 'none' },\n  })\n})","handlingStrategy":"validation","validationCode":"// Validate every addUtilities selector before registering\nconst VALID_CLASS_NAME = /^[a-z@][a-zA-Z0-9/%._-]*$/;\nfunction safeAddUtilities(addUtilities, utilities) {\n  for (const selector of Object.keys(utilities)) {\n    const ast = parseSelector(selector); // your selector parser\n    const hasValid = ast.some(node =>\n      node.kind === 'selector' &&\n      node.value[0] === '.' &&\n      VALID_CLASS_NAME.test(node.value.slice(1))\n    );\n    if (!hasValid) {\n      console.warn(`Skipping invalid utility selector: ${selector}`);\n      delete utilities[selector];\n    }\n  }\n  addUtilities(utilities);\n}","typeGuard":"function isValidUtilitySelector(selector: string): boolean {\n  // Must contain at least one .class whose name matches the v4 rule\n  const matches = selector.match(/\\.([a-z@][a-zA-Z0-9/%._-]*)/g);\n  return Array.isArray(matches) && matches.length > 0;\n}","tryCatchPattern":"try {\n  addUtilities({ [name]: css });\n} catch (e) {\n  if (e instanceof Error && e.message.includes('invalid utility selector')) {\n    console.warn(`Plugin skipped invalid selector: ${name}`);\n  } else {\n    throw e;\n  }\n}","preventionTips":["Always anchor addUtilities selectors on a single lowercase-led class.","Run a unit test over plugin selectors asserting they match /^\\.[a-z@][a-zA-Z0-9/%._-]*$/.","Move non-class structural CSS to addBase.","Upgrade third-party plugins to v4-compatible versions."],"tags":["tailwind-v4","plugin-api","addutilities","selector","compat"],"backgroundTag":null,"analyzedSha":"16e94cbf7f965c5ad697e90e940b5e178efad67c","analyzedAt":"2026-08-12T06:02:42.469Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}