Skip to content

Fix @utility to accept already-escaped utility names#19691

Closed
shtse8 wants to merge 1 commit intotailwindlabs:mainfrom
shtse8:fix/escaped-utility-names
Closed

Fix @utility to accept already-escaped utility names#19691
shtse8 wants to merge 1 commit intotailwindlabs:mainfrom
shtse8:fix/escaped-utility-names

Conversation

@shtse8
Copy link

@shtse8 shtse8 commented Feb 17, 2026

This PR fixes a bug where using CSS escape sequences in @utility names would throw a validation error, even though escaping is required by strict CSS linters like Biome.

Problem

Writing @utility my/utility is technically invalid CSS (an unescaped / in an identifier). Strict linters like Biome will flag this with a parse error. The correct CSS syntax is @utility my\/utility (with the slash escaped).

However, Tailwind currently throws:

`@utility my/utility` defines an invalid utility name. Utilities should be alphanumeric and start with a lowercase letter.

This is because the CSS parser preserves raw escape sequences in node.params, and the validator didn't account for CSS escape sequences.

Fix

Unescape the utility name before validation and registration in createCssUtility, using the existing unescape utility from utils/escape.ts. This means:

  • @utility my\/utility (escaped) is accepted and treated as equivalent to @utility my/utility (unescaped)
  • The utility is registered under the unescaped name (my/utility) so that candidates in HTML continue to match correctly
  • No double-escaping: the generated CSS selector is .my\/button in both cases

Test plan

  1. Existing tests pass
  2. Added a test verifying @utility my\/utility generates .my\/utility { … }
  3. Added a test verifying the escaped and unescaped forms produce identical output

Fixes #19607

When using CSS linters like Biome that require proper CSS escaping,
writing `@utility my\/utility` (with the slash escaped as `\/`) is
the correct way to express a utility name containing a forward slash.

Previously, Tailwind would throw an error:

  `@utility my\/utility` defines an invalid utility name. Utilities
  should be alphanumeric and start with a lowercase letter.

This is because the CSS parser preserves raw escape sequences and the
validator did not account for CSS escape sequences in utility names.

The fix unescapes the utility name (using the same CSS unescape
implementation that already exists in `utils/escape.ts`) before
validating and registering it, so that:

- `@utility my\/utility` (escaped) is treated as equivalent to
  `@utility my/utility` (unescaped)
- The utility is registered under the unescaped name so that candidates
  like `my/utility` in HTML continue to match correctly
- No double-escaping occurs: the generated CSS selector is
  `.my\/utility` in both cases

Fixes tailwindlabs#19607
@shtse8 shtse8 requested a review from a team as a code owner February 17, 2026 20:30
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 17, 2026

No actionable comments were generated in the recent review. 🎉


Walkthrough

The pull request adds support for handling CSS escape sequences in @utility directive names. Implementation changes include importing an unescape function and applying it during utility name parsing in createCssUtility. Test coverage is extended with two new test cases: one validating that escaped utility names (e.g., my\/utility) compile correctly, and another confirming that escaped and unescaped forms produce identical output. The changes affect utility name resolution without modifying existing compilation logic.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: fixing @utility to accept escaped utility names, which is the core objective of the PR.
Description check ✅ Passed The description comprehensively explains the problem, the fix applied, and test coverage, directly relating to the changeset modifications.
Linked Issues check ✅ Passed The PR fully addresses issue #19607 by unescaping utility names before validation, accepting escaped forms like @utility my/utility and treating them equivalently to unescaped forms without double-escaping.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing the @utility escape sequence handling: modified utilities.ts to unescape names and added comprehensive test coverage in index.test.ts.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@RobinMalfait
Copy link
Member

Hey! I appreciate the PR but please only open PRs for issues that don't have an open PR already.

Going to close this in favor of the PR that solves this issue already: #19626

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

@utility doesn't allow for escaped utility names

2 participants

Comments