diff --git a/CHANGELOG.md b/CHANGELOG.md index 99b35a7d755e..3514751e54e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Substitute `@variant` inside legacy JS APIs ([#19263](https://github.com/tailwindlabs/tailwindcss/pull/19263)) +- Escape variant modifiers when used in selectors and at-rules ([#19269](https://github.com/tailwindlabs/tailwindcss/pull/19269)) ### Added diff --git a/packages/tailwindcss/src/variants.test.ts b/packages/tailwindcss/src/variants.test.ts index a2540e698d7b..2fc3e5449eb4 100644 --- a/packages/tailwindcss/src/variants.test.ts +++ b/packages/tailwindcss/src/variants.test.ts @@ -586,11 +586,13 @@ test('group-*', async () => { 'group-hover:group-focus:flex', 'group-focus:group-hover:flex', + + 'group-hover/foo+bar:flex', ], ), ).toMatchInlineSnapshot(` "@media (hover: hover) { - .group-hover\\:flex:is(:where(.group):hover *) { + .group-hover\\:flex:is(:where(.group):hover *), .group-hover\\/foo\\+bar\\:flex:is(:where(.group\\/foo\\+bar):hover *) { display: flex; } } @@ -677,11 +679,13 @@ test('peer-*', async () => { 'peer-hocus:flex', 'peer-hover:peer-focus:flex', 'peer-focus:peer-hover:flex', + + 'peer-hover/foo+bar:flex', ], ), ).toMatchInlineSnapshot(` "@media (hover: hover) { - .peer-hover\\:flex:is(:where(.peer):hover ~ *) { + .peer-hover\\:flex:is(:where(.peer):hover ~ *), .peer-hover\\/foo\\+bar\\:flex:is(:where(.peer\\/foo\\+bar):hover ~ *) { display: flex; } } @@ -2081,6 +2085,7 @@ test('container queries', async () => { [ '@lg:flex', '@lg/name:flex', + '@lg/foo+bar:flex', '@[123px]:flex', '@[456px]/name:flex', '@foo-bar:flex', @@ -2088,6 +2093,7 @@ test('container queries', async () => { '@min-lg:flex', '@min-lg/name:flex', + '@min-lg/foo+bar:flex', '@min-[123px]:flex', '@min-[456px]/name:flex', '@min-foo-bar:flex', @@ -2095,6 +2101,7 @@ test('container queries', async () => { '@max-lg:flex', '@max-lg/name:flex', + '@max-lg/foo+bar:flex', '@max-[123px]:flex', '@max-[456px]/name:flex', '@max-foo-bar:flex', @@ -2114,6 +2121,12 @@ test('container queries', async () => { } } + @container foo\\+bar not (min-width: 1024px) { + .\\@max-lg\\/foo\\+bar\\:flex { + display: flex; + } + } + @container name not (min-width: 1024px) { .\\@max-lg\\/name\\:flex { display: flex; @@ -2150,6 +2163,12 @@ test('container queries', async () => { } } + @container foo\\+bar (min-width: 1024px) { + .\\@lg\\/foo\\+bar\\:flex { + display: flex; + } + } + @container name (min-width: 1024px) { .\\@lg\\/name\\:flex { display: flex; @@ -2162,6 +2181,12 @@ test('container queries', async () => { } } + @container foo\\+bar (min-width: 1024px) { + .\\@min-lg\\/foo\\+bar\\:flex { + display: flex; + } + } + @container name (min-width: 1024px) { .\\@min-lg\\/name\\:flex { display: flex; diff --git a/packages/tailwindcss/src/variants.ts b/packages/tailwindcss/src/variants.ts index 82a2b8592cfe..e7883bdfe2bf 100644 --- a/packages/tailwindcss/src/variants.ts +++ b/packages/tailwindcss/src/variants.ts @@ -17,6 +17,7 @@ import type { DesignSystem } from './design-system' import type { Theme } from './theme' import { compareBreakpoints } from './utils/compare-breakpoints' import { DefaultMap } from './utils/default-map' +import { escape } from './utils/escape' import { isPositiveInteger } from './utils/infer-data-type' import { segment } from './utils/segment' import { walk, WalkAction } from './walk' @@ -522,7 +523,7 @@ export function createVariants(theme: Theme): Variants { // Name the group by appending the modifier to `group` class itself if // present. let variantSelector = variant.modifier - ? `:where(.${theme.prefix ? `${theme.prefix}\\:` : ''}group\\/${variant.modifier.value})` + ? `:where(.${theme.prefix ? `${theme.prefix}\\:` : ''}group\\/${escape(variant.modifier.value)})` : `:where(.${theme.prefix ? `${theme.prefix}\\:` : ''}group)` let didApply = false @@ -574,7 +575,7 @@ export function createVariants(theme: Theme): Variants { // Name the peer by appending the modifier to `peer` class itself if // present. let variantSelector = variant.modifier - ? `:where(.${theme.prefix ? `${theme.prefix}\\:` : ''}peer\\/${variant.modifier.value})` + ? `:where(.${theme.prefix ? `${theme.prefix}\\:` : ''}peer\\/${escape(variant.modifier.value)})` : `:where(.${theme.prefix ? `${theme.prefix}\\:` : ''}peer)` let didApply = false @@ -1063,7 +1064,7 @@ export function createVariants(theme: Theme): Variants { atRule( '@container', variant.modifier - ? `${variant.modifier.value} (width < ${value})` + ? `${escape(variant.modifier.value)} (width < ${value})` : `(width < ${value})`, ruleNode.nodes, ), @@ -1092,7 +1093,7 @@ export function createVariants(theme: Theme): Variants { atRule( '@container', variant.modifier - ? `${variant.modifier.value} (width >= ${value})` + ? `${escape(variant.modifier.value)} (width >= ${value})` : `(width >= ${value})`, ruleNode.nodes, ), @@ -1110,7 +1111,7 @@ export function createVariants(theme: Theme): Variants { atRule( '@container', variant.modifier - ? `${variant.modifier.value} (width >= ${value})` + ? `${escape(variant.modifier.value)} (width >= ${value})` : `(width >= ${value})`, ruleNode.nodes, ),