From e3bba73515ecfe233e29b7398cec9847b9951dc2 Mon Sep 17 00:00:00 2001 From: Aaron Tinio Date: Sun, 22 Mar 2026 07:12:24 +0800 Subject: [PATCH 1/5] fix(canonicalize): collapse arbitrary values into shorthand utilities MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `dynamicUtilities` discovers collapse targets by swapping utility roots (e.g., `px` → `p`), but the guard at line 326 restricted this to named values. This was likely intentional for the initial implementation, but `cloneCandidate` and `printCandidate` both handle arbitrary values correctly, so the restriction can be safely relaxed. Without this, arbitrary values like `px-[1.2rem] py-[1.2rem]` were never collapsed into `p-[1.2rem]` — which is what caused the non-deterministic `--stream` output reported in #19835. Fixes #19835 --- .../src/canonicalize-candidates.test.ts | 16 ++++++++++++++++ .../tailwindcss/src/canonicalize-candidates.ts | 5 +---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/packages/tailwindcss/src/canonicalize-candidates.test.ts b/packages/tailwindcss/src/canonicalize-candidates.test.ts index fef9eee08008..ed3969aab408 100644 --- a/packages/tailwindcss/src/canonicalize-candidates.test.ts +++ b/packages/tailwindcss/src/canonicalize-candidates.test.ts @@ -1310,3 +1310,19 @@ test( ) }, ) + +test('collapse canonicalization works for arbitrary values', { timeout }, async () => { + let designSystem = await designSystems.get(__dirname).get(css` + @import 'tailwindcss'; + `) + + let options: CanonicalizeOptions = { + collapse: true, + logicalToPhysical: true, + rem: 16, + } + + expect( + designSystem.canonicalizeCandidates(['px-[1.2rem]', 'py-[1.2rem]', 'text-left'], options), + ).toEqual(['text-left', 'p-[1.2rem]']) +}) diff --git a/packages/tailwindcss/src/canonicalize-candidates.ts b/packages/tailwindcss/src/canonicalize-candidates.ts index eaa055b25dd4..6f48f7c1c5b0 100644 --- a/packages/tailwindcss/src/canonicalize-candidates.ts +++ b/packages/tailwindcss/src/canonicalize-candidates.ts @@ -321,10 +321,7 @@ function collapseCandidates(options: InternalCanonicalizeOptions, candidates: st if (relevantProperties.size === 0) return result for (let parsedCandidate of parseCandidate(designSystem, candidate)) { - if ( - parsedCandidate.kind !== 'functional' || - parsedCandidate.value?.kind !== 'named' // Necessary for bare values - ) { + if (parsedCandidate.kind !== 'functional' || parsedCandidate.value === null) { continue } From 4465896559bd2c2c138ed31d1e17930456ae9121 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Mon, 23 Mar 2026 11:01:29 +0100 Subject: [PATCH 2/5] link issue to test --- packages/tailwindcss/src/canonicalize-candidates.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/tailwindcss/src/canonicalize-candidates.test.ts b/packages/tailwindcss/src/canonicalize-candidates.test.ts index ed3969aab408..171e6c812ca9 100644 --- a/packages/tailwindcss/src/canonicalize-candidates.test.ts +++ b/packages/tailwindcss/src/canonicalize-candidates.test.ts @@ -1311,6 +1311,7 @@ test( }, ) +// https://github.com/tailwindlabs/tailwindcss/issues/19835 test('collapse canonicalization works for arbitrary values', { timeout }, async () => { let designSystem = await designSystems.get(__dirname).get(css` @import 'tailwindcss'; From b2f5b1afc1ba116ca73962e18618504054557e62 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Mon, 23 Mar 2026 11:06:14 +0100 Subject: [PATCH 3/5] add additional test to ensure arbitrary value to bare value --- .../src/canonicalize-candidates.test.ts | 40 +++++++++++++------ 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/packages/tailwindcss/src/canonicalize-candidates.test.ts b/packages/tailwindcss/src/canonicalize-candidates.test.ts index 171e6c812ca9..f83c7d51454e 100644 --- a/packages/tailwindcss/src/canonicalize-candidates.test.ts +++ b/packages/tailwindcss/src/canonicalize-candidates.test.ts @@ -1312,18 +1312,32 @@ test( ) // https://github.com/tailwindlabs/tailwindcss/issues/19835 -test('collapse canonicalization works for arbitrary values', { timeout }, async () => { - let designSystem = await designSystems.get(__dirname).get(css` - @import 'tailwindcss'; - `) +test.each([ + // Arbitrary values should be collapsed to another arbitrary value + [ + ['px-[1.2rem]', 'py-[1.2rem]', 'text-left'], + ['text-left', 'p-[1.2rem]'], + ], + + // Arbitrary values could also be collapsed in a bare value + [ + ['px-[30.75rem]', 'py-[30.75rem]', 'text-left'], + ['text-left', 'p-123'], + ], +])( + 'collapse canonicalization works for arbitrary values', + { timeout }, + async (candidates, expected) => { + let designSystem = await designSystems.get(__dirname).get(css` + @import 'tailwindcss'; + `) - let options: CanonicalizeOptions = { - collapse: true, - logicalToPhysical: true, - rem: 16, - } + let options: CanonicalizeOptions = { + collapse: true, + logicalToPhysical: true, + rem: 16, + } - expect( - designSystem.canonicalizeCandidates(['px-[1.2rem]', 'py-[1.2rem]', 'text-left'], options), - ).toEqual(['text-left', 'p-[1.2rem]']) -}) + expect(designSystem.canonicalizeCandidates(candidates, options)).toEqual(expected) + }, +) From 89f8bab4e2c2cd39cce3681595adc0d050206f62 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Mon, 23 Mar 2026 11:13:31 +0100 Subject: [PATCH 4/5] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fccae8e6f7a3..fa59703b512e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Improve canonicalizations for `tracking-*` utilities ([#19827](https://github.com/tailwindlabs/tailwindcss/pull/19827)) - Fix crash due to invalid characters in candidate ([#19829](https://github.com/tailwindlabs/tailwindcss/pull/19829)) - Ensure query params in imports are considered unique resources when using `@tailwindcss/webpack` ([#19723](https://github.com/tailwindlabs/tailwindcss/pull/19723)) +- Collapse arbitrary values into shorthand utilities during canonicalization ([#19837](https://github.com/tailwindlabs/tailwindcss/pull/19837)) ## [4.2.2] - 2026-03-18 From fe3e48ad621f17afc5f0251d1628870692ce0647 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Mon, 23 Mar 2026 11:14:35 +0100 Subject: [PATCH 5/5] fix typo --- packages/tailwindcss/src/canonicalize-candidates.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/tailwindcss/src/canonicalize-candidates.test.ts b/packages/tailwindcss/src/canonicalize-candidates.test.ts index f83c7d51454e..c6d2068b7572 100644 --- a/packages/tailwindcss/src/canonicalize-candidates.test.ts +++ b/packages/tailwindcss/src/canonicalize-candidates.test.ts @@ -1319,7 +1319,7 @@ test.each([ ['text-left', 'p-[1.2rem]'], ], - // Arbitrary values could also be collapsed in a bare value + // Arbitrary values could also be collapsed into a bare value [ ['px-[30.75rem]', 'py-[30.75rem]', 'text-left'], ['text-left', 'p-123'],