feat: handle backslash in @utility name (#19626)

Resolves #19607

<!--

👋 Hey, thanks for your interest in contributing to Tailwind!

**Please ask first before starting work on any significant new
features.**

It's never a fun experience to have your pull request declined after
investing a lot of time and effort into a new feature. To avoid this
from happening, we request that contributors create a discussion to
first discuss any significant new features.

For more info, check out the contributing guide:


https://github.com/tailwindcss/tailwindcss/blob/main/.github/CONTRIBUTING.md

-->

## Summary

<!--

Provide a summary of the issue and the changes you're making. How does
your change solve the problem?

-->

I believe there is no obstacle to simply ignoring backslashes in the
name. This way, various validators - which are not aware of Tailwind
CSS's specific syntax (which allows the `/` character to be used
directly in utility names) - can still be bypassed using backslashes.
For example, instead of `@utility push-1/2`, one could use `@utility
push-1\/2`, while the end result would be identical.

## Test plan

<!--

Explain how you tested your changes. Include the exact commands that you
used to verify the change works and include screenshots/screen
recordings of the update behavior in the browser if applicable.

-->

I took a previous utility test as a baseline and extended it with
backslashes, and I expect the same result in the output as in the
original test case:

*
https://github.com/rozsazoltan/tailwindcss/blob/main/packages/tailwindcss/src/utilities.test.ts#L28553-L28567
(the original test case I started from)
*
https://github.com/rozsazoltan/tailwindcss/blob/feat/handle-blackslash-in-utility-name/packages/tailwindcss/src/index.test.ts#L4640-L4654
(the current PR's test)

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
This commit is contained in:
Rózsa Zoltán 2026-02-17 22:12:20 +01:00 • committed by GitHub
parent bc6e4b85bf
commit ed52d3e6c9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 32 additions and 1 deletions

View file

@ -30,6 +30,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Ensure changes to external files listed via `@source` trigger a full page reload when using `@tailwindcss/vite` ([#19670](https://github.com/tailwindlabs/tailwindcss/pull/19670))
- Improve performance Oxide scanner in bigger projects ([#19632](https://github.com/tailwindlabs/tailwindcss/pull/19632))
- Ensure import aliases in Astro v5 work without crashing ([#19677](https://github.com/tailwindlabs/tailwindcss/issues/19677))
- Allow escape characters in `@utility` names to improve support with formatters such as Biome ([#19626](https://github.com/tailwindlabs/tailwindcss/pull/19626))
### Deprecated

View file

@ -4729,6 +4729,31 @@ describe('@utility', () => {
)
})
test('@utility can handle escape sequences correctly', async () => {
let { build } = await compile(css`
@layer utilities {
@tailwind utilities;
}
@utility push-1\/2 {
right: 50%;
}
@utility push-50\% {
right: 50%;
}
`)
let compiled = build(['push-1/2', 'push-50%'])
expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(`
"@layer utilities {
.push-1\\/2, .push-50\\% {
right: 50%;
}
}"
`)
})
test('A functional @utility must end in -*', () => {
return expect(
compileCss(css`

View file

@ -16,6 +16,7 @@ import { enableContainerSizeUtility } from './feature-flags'
import type { Theme, ThemeKey } from './theme'
import { compareBreakpoints } from './utils/compare-breakpoints'
import { DefaultMap } from './utils/default-map'
import { unescape } from './utils/escape'
import {
inferDataType,
isPositiveInteger,
@ -5939,7 +5940,11 @@ export const BARE_VALUE_DATA_TYPES = [
]
export function createCssUtility(node: AtRule) {
let name = node.params
// Allow escaped characters in the name for compatibility with formatters and
// other parsers, to ensure valid CSS syntax. E.g.: `@utility foo-1\/2`.
//
// Note: the actual utility will be `foo-1/2`
let name = unescape(node.params)
// Functional utilities. E.g.: `tab-size-*`
if (isValidFunctionalUtilityName(name)) {