tailwindlabs/tailwindcss · error · Error
`matchUtilities({ '${name}' : … })` defines an invalid utili
Error message
`matchUtilities({ '${name}' : … })` defines an invalid utility name. Utilities should be alphanumeric and start with a lowercase letter, eg. `scrollbar`. What it means
Thrown by the compatibility layer's `matchUtilities` API when a key in the `utilities` object fails the `IS_VALID_UTILITY_NAME` regex (`/^[a-z@][a-zA-Z0-9/%._-]*$/`). Unlike `addUtilities` selectors, `matchUtilities` keys are bare names (no leading dot) that become functional utilities like `name-[value]`, so they must be alphanumeric-friendly and start with a lowercase letter (or @). An invalid name cannot be compiled into a candidate, so registration is aborted.
Source
Thrown at packages/tailwindcss/src/compat/plugin-api.ts:366
designSystem.utilities.static(className, (candidate) => {
let clonedAst = ast.map(cloneAstNode)
replaceNestedClassNameReferences(clonedAst, className, candidate.raw)
featuresRef.current |= substituteAtApply(clonedAst, designSystem)
return clonedAst
})
}
},
matchUtilities(utilities, options) {
let types = options?.type
? Array.isArray(options?.type)
? options.type
: [options.type]
: ['any']
for (let [name, fn] of Object.entries(utilities)) {
if (!IS_VALID_UTILITY_NAME.test(name)) {
throw new Error(
`\`matchUtilities({ '${name}' : … })\` defines an invalid utility name. Utilities should be alphanumeric and start with a lowercase letter, eg. \`scrollbar\`.`,
)
}
function compileFn({ negative }: { negative: boolean }) {
return (candidate: Extract<Candidate, { kind: 'functional' }>) => {
// Throw out any candidate whose value is not a supported type
if (
candidate.value?.kind === 'arbitrary' &&
types.length > 0 &&
!types.includes('any')
) {
// The candidate has an explicit data type but it's not in the list
// of supported types by this utility. For example, a `scrollbar`
// utility that is only used to change the scrollbar color but is
// used with a `length` value: `scrollbar-[length:var(--whatever)]`
if (candidate.value.dataType && !types.includes(candidate.value.dataType)) {
returnView on GitHub (pinned to 16e94cbf7f)
Solutions
- Rename the utility key to start with a lowercase letter and use only `[a-zA-Z0-9/%._-]` afterwards, e.g. `scroll`, `col-span`.
- Strip any leading dot, space, or underscore from the key — `matchUtilities` wants the bare name, not a selector.
- If you need a negative or prefixed variant, rely on the `options.negative` / theme prefix machinery rather than baking symbols into the name.
- Validate every key with `/^[a-z@][a-zA-Z0-9/%._-]*$/.test(name)` in your plugin before calling `matchUtilities`.
Example fix
// before
matchUtilities({
'Scrollbar': (value) => ({ scrollbarColor: value }),
})
// after
matchUtilities({
'scrollbar': (value) => ({ scrollbarColor: value }),
}) Defensive patterns
Strategy: validation
Validate before calling
const IS_VALID_UTILITY_NAME = /^[a-z@][a-zA-Z0-9/%._-]*$/;
function safeMatchUtilities(matchUtilities, utilities, options) {
const filtered = {};
for (const [name, fn] of Object.entries(utilities)) {
if (IS_VALID_UTILITY_NAME.test(name)) {
filtered[name] = fn;
} else {
console.warn(`Skipping invalid matchUtilities name: ${JSON.stringify(name)}`);
}
}
matchUtilities(filtered, options);
} Type guard
function isValidUtilityName(name: string): boolean {
return /^[a-z@][a-zA-Z0-9/%._-]*$/.test(name);
} Try / catch
try {
matchUtilities(utilities, options);
} catch (e) {
if (e instanceof Error && e.message.includes('invalid utility name')) {
// filter and retry, or warn
} else throw e;
} Prevention
- Name matchUtilities keys as bare lowercase-first identifiers.
- Reject keys containing spaces, leading dots, or leading underscores in CI lint.
- Document the allowed character set in your plugin readme.
When it happens
Trigger: A plugin calls `matchUtilities({ 'Scroll': fn })` (capital first letter), `matchUtilities({ '1col': fn })` (leading digit), `matchUtilities({ 'col width': fn })` (space, not in allowed set), or `matchUtilities({ '_foo': fn })` (leading underscore — underscore is allowed only after the first char). The check runs for every entry via `Object.entries(utilities)`, so one bad key fails the whole call.
Common situations: Migrating a v3 plugin whose utility name began with an underscore or capital; copy-paste from a config object that used display labels; plugin authors assuming `matchUtilities` accepts the same selector syntax as `addUtilities`.
Related errors
- `addUtilities({ '${name}' : … })` defines an invalid utility
- The prefix "${resolvedConfig.prefix}" is invalid. Prefixes m
- `addVariant('${name}')` defines an invalid variant name. Var
- The --alpha(…) function requires a color and an alpha value,
- The --alpha(…) function only accepts one argument, e.g.: `--
AI-assisted analysis of tailwindlabs/tailwindcss@16e94cbf7f (2026-08-12).
Data as JSON: /api/errors/f758ca0f427f5d65.
Report an issue: GitHub.