tailwindlabs/tailwindcss · error · Error
`addUtilities({ '${name}' : … })` defines an invalid utility
Error message
`addUtilities({ '${name}' : … })` defines an invalid utility selector. Utilities must be a single class name and start with a lowercase letter, eg. `.scrollbar-none`. What it means
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.
Source
Thrown at packages/tailwindcss/src/compat/plugin-api.ts:326
node.value = value
return
}
if (
node.kind === 'function' &&
(node.value === ':not' ||
// A class inside `:nth-child(… of <selector>)` is part of the
// condition, not a utility being defined.
node.value === ':nth-child' ||
node.value === ':nth-last-child')
) {
return WalkAction.Skip
}
})
if (!foundValidUtility) {
throw new Error(
`\`addUtilities({ '${name}' : … })\` defines an invalid utility selector. Utilities must be a single class name and start with a lowercase letter, eg. \`.scrollbar-none\`.`,
)
}
}
for (let [className, ast] of utils) {
// Prefix all class selector with the configured theme prefix
if (designSystem.theme.prefix) {
walk(ast, (node) => {
if (node.kind === 'rule') {
let selectorAst = SelectorParser.parse(node.selector)
walk(selectorAst, (node) => {
if (node.kind === 'selector' && node.value[0] === '.') {
node.value = `.${designSystem.theme.prefix}\\:${node.value.slice(1)}`
}
})
node.selector = SelectorParser.toCss(selectorAst)
}View on GitHub (pinned to 16e94cbf7f)
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.
Example fix
// before
plugin(function ({ addUtilities }) {
addUtilities({
'.Scrollbar-none': { scrollbarWidth: 'none' },
})
})
// after
plugin(function ({ addUtilities }) {
addUtilities({
'.scrollbar-none': { scrollbarWidth: 'none' },
})
}) Defensive patterns
Strategy: validation
Validate before calling
// Validate every addUtilities selector before registering
const VALID_CLASS_NAME = /^[a-z@][a-zA-Z0-9/%._-]*$/;
function safeAddUtilities(addUtilities, utilities) {
for (const selector of Object.keys(utilities)) {
const ast = parseSelector(selector); // your selector parser
const hasValid = ast.some(node =>
node.kind === 'selector' &&
node.value[0] === '.' &&
VALID_CLASS_NAME.test(node.value.slice(1))
);
if (!hasValid) {
console.warn(`Skipping invalid utility selector: ${selector}`);
delete utilities[selector];
}
}
addUtilities(utilities);
} Type guard
function isValidUtilitySelector(selector: string): boolean {
// Must contain at least one .class whose name matches the v4 rule
const matches = selector.match(/\.([a-z@][a-zA-Z0-9/%._-]*)/g);
return Array.isArray(matches) && matches.length > 0;
} Try / catch
try {
addUtilities({ [name]: css });
} catch (e) {
if (e instanceof Error && e.message.includes('invalid utility selector')) {
console.warn(`Plugin skipped invalid selector: ${name}`);
} else {
throw e;
}
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- `matchUtilities({ '${name}' : … })` defines an invalid utili
- The --alpha(…) function requires a color and an alpha value,
- The --alpha(…) function only accepts one argument, e.g.: `--
- The --spacing(…) function requires an argument, but received
- The --spacing(…) function only accepts a single argument, bu
AI-assisted analysis of tailwindlabs/tailwindcss@16e94cbf7f (2026-08-12).
Data as JSON: /api/errors/547b9147036f77af.
Report an issue: GitHub.