diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b24c91399c8..3ea4c804c13d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Ensure `color-mix(…)` polyfills do not cause used CSS variables to be removed ([#17555](https://github.com/tailwindlabs/tailwindcss/pull/17555)) - Ensure the `color-mix(…)` polyfill creates fallbacks for theme variables that reference other theme variables ([#17562](https://github.com/tailwindlabs/tailwindcss/pull/17562)) - Fix brace expansion in `@source inline('z-{10..0}')` with range going down ([#17591](https://github.com/tailwindlabs/tailwindcss/pull/17591)) +- Ensure container query variant names can contain hyphens ([#17628](https://github.com/tailwindlabs/tailwindcss/pull/17628)) ## [4.1.3] - 2025-04-04 diff --git a/packages/tailwindcss/src/candidate.test.ts b/packages/tailwindcss/src/candidate.test.ts index 61be4378afda..3c54bef7a20d 100644 --- a/packages/tailwindcss/src/candidate.test.ts +++ b/packages/tailwindcss/src/candidate.test.ts @@ -1294,6 +1294,36 @@ it('should parse a functional variant starting with @', () => { `) }) +it('should parse a functional variant starting with @ that has a hyphen', () => { + let utilities = new Utilities() + utilities.static('flex', () => []) + + let variants = new Variants() + variants.functional('@', () => {}) + + expect(run('@foo-bar:flex', { utilities, variants })).toMatchInlineSnapshot(` + [ + { + "important": false, + "kind": "static", + "raw": "@foo-bar:flex", + "root": "flex", + "variants": [ + { + "kind": "functional", + "modifier": null, + "root": "@", + "value": { + "kind": "named", + "value": "foo-bar", + }, + }, + ], + }, + ] + `) +}) + it('should parse a functional variant starting with @ and a modifier', () => { let utilities = new Utilities() utilities.static('flex', () => []) diff --git a/packages/tailwindcss/src/candidate.ts b/packages/tailwindcss/src/candidate.ts index 406a16aad1b5..27a6b6294a3f 100644 --- a/packages/tailwindcss/src/candidate.ts +++ b/packages/tailwindcss/src/candidate.ts @@ -745,14 +745,6 @@ function* findRoots(input: string, exists: (input: string) => boolean): Iterable // Otherwise test every permutation of the input by iteratively removing // everything after the last dash. let idx = input.lastIndexOf('-') - if (idx === -1) { - // Variants starting with `@` are special because they don't need a `-` - // after the `@` (E.g.: `@-lg` should be written as `@lg`). - if (input[0] === '@' && exists('@')) { - yield ['@', input.slice(1)] - } - return - } // Determine the root and value by testing permutations of the incoming input. // @@ -761,7 +753,7 @@ function* findRoots(input: string, exists: (input: string) => boolean): Iterable // `bg-red-500` -> No match // `bg-red` -> No match // `bg` -> Match - do { + while (idx > 0) { let maybeRoot = input.slice(0, idx) if (exists(maybeRoot)) { @@ -776,5 +768,11 @@ function* findRoots(input: string, exists: (input: string) => boolean): Iterable } idx = input.lastIndexOf('-', idx - 1) - } while (idx > 0) + } + + // Try '@' variant after permutations. This allows things like `@max` of `@max-foo-bar` + // to match before looking for `@`. + if (input[0] === '@' && exists('@')) { + yield ['@', input.slice(1)] + } } diff --git a/packages/tailwindcss/src/variants.test.ts b/packages/tailwindcss/src/variants.test.ts index d6fed0f59208..5da1dcac7572 100644 --- a/packages/tailwindcss/src/variants.test.ts +++ b/packages/tailwindcss/src/variants.test.ts @@ -2074,6 +2074,7 @@ test('container queries', async () => { css` @theme { --container-lg: 1024px; + --container-foo-bar: 1440px; } @tailwind utilities; `, @@ -2082,20 +2083,38 @@ test('container queries', async () => { '@lg/name:flex', '@[123px]:flex', '@[456px]/name:flex', + '@foo-bar:flex', + '@foo-bar/name:flex', '@min-lg:flex', '@min-lg/name:flex', '@min-[123px]:flex', '@min-[456px]/name:flex', + '@min-foo-bar:flex', + '@min-foo-bar/name:flex', '@max-lg:flex', '@max-lg/name:flex', '@max-[123px]:flex', '@max-[456px]/name:flex', + '@max-foo-bar:flex', + '@max-foo-bar/name:flex', ], ), ).toMatchInlineSnapshot(` - "@container name not (min-width: 1024px) { + "@container name not (min-width: 1440px) { + .\\@max-foo-bar\\/name\\:flex { + display: flex; + } + } + + @container not (min-width: 1440px) { + .\\@max-foo-bar\\:flex { + display: flex; + } + } + + @container name not (min-width: 1024px) { .\\@max-lg\\/name\\:flex { display: flex; } @@ -2153,6 +2172,30 @@ test('container queries', async () => { .\\@min-lg\\:flex { display: flex; } + } + + @container name (min-width: 1440px) { + .\\@foo-bar\\/name\\:flex { + display: flex; + } + } + + @container (min-width: 1440px) { + .\\@foo-bar\\:flex { + display: flex; + } + } + + @container name (min-width: 1440px) { + .\\@min-foo-bar\\/name\\:flex { + display: flex; + } + } + + @container (min-width: 1440px) { + .\\@min-foo-bar\\:flex { + display: flex; + } }" `) })