diff --git a/packages/tailwindcss/src/canonicalize-candidates.ts b/packages/tailwindcss/src/canonicalize-candidates.ts index 13c3bc257a91..d45793ca6c1a 100644 --- a/packages/tailwindcss/src/canonicalize-candidates.ts +++ b/packages/tailwindcss/src/canonicalize-candidates.ts @@ -912,6 +912,17 @@ function arbitraryUtilities(candidate: Candidate, options: InternalCanonicalizeO return candidate } + // Guard — skip when value contains complex expressions that shouldn't be converted + // Allow theme() functions and color: data types, but block complex var() expressions + if (candidate.kind === 'functional' && candidate.value?.kind === 'arbitrary') { + const raw = String(candidate.value.value ?? '') + // Skip if it contains calc() or complex var() expressions (with spaces, commas, slashes) + // But allow simple var(--color-name) patterns from theme() functions + if (/calc\(|\s|,|\//.test(raw) && !/theme\(|color:/.test(raw)) { + return candidate + } + } + let designSystem = options.designSystem let utilities = designSystem.storage[PRE_COMPUTED_UTILITIES_KEY].get(options.signatureOptions) let signatures = designSystem.storage[UTILITY_SIGNATURE_KEY].get(options.signatureOptions) @@ -997,6 +1008,10 @@ function arbitraryUtilities(candidate: Candidate, options: InternalCanonicalizeO spacingMultiplier = Math.abs(spacingMultiplier) } + // normalize and detect complex values + const valueStr = String(value) + const isComplex = /var\(--[^)]+\)|calc\(|\s|,|\//.test(valueStr) + for (let root of Array.from(designSystem.utilities.keys('functional')).sort( // Sort negative roots after positive roots so that we can try // `mt-*` before `-mt-*`. This is especially useful in situations where @@ -1006,17 +1021,23 @@ function arbitraryUtilities(candidate: Candidate, options: InternalCanonicalizeO if (rootPrefix) root = `${rootPrefix}${root}` // Try as bare value - for (let replacementCandidate of parseCandidate(designSystem, `${root}-${value}`)) { - yield replacementCandidate + // ✅ changed: skip bare value for complex values; use valueStr + if (!isComplex) { + for (let replacementCandidate of parseCandidate(designSystem, `${root}-${valueStr}`)) { + yield replacementCandidate + } } // Try as bare value with modifier if (candidate.modifier) { - for (let replacementCandidate of parseCandidate( - designSystem, - `${root}-${value}${candidate.modifier}`, - )) { - yield replacementCandidate + // guard complex & use printModifier + valueStr + if (!isComplex) { + for (let replacementCandidate of parseCandidate( + designSystem, + `${root}-${valueStr}${printModifier(candidate.modifier)}` + )) { + yield replacementCandidate + } } } @@ -1043,7 +1064,8 @@ function arbitraryUtilities(candidate: Candidate, options: InternalCanonicalizeO } // Try as arbitrary value - for (let replacementCandidate of parseCandidate(designSystem, `${root}-[${value}]`)) { + // ✅ changed: use valueStr to keep normalized/bracketed form + for (let replacementCandidate of parseCandidate(designSystem, `${root}-[${valueStr}]`)) { // ✅ changed yield replacementCandidate } @@ -1051,7 +1073,7 @@ function arbitraryUtilities(candidate: Candidate, options: InternalCanonicalizeO if (candidate.modifier) { for (let replacementCandidate of parseCandidate( designSystem, - `${root}-[${value}]${printModifier(candidate.modifier)}`, + `${root}-[${valueStr}]${printModifier(candidate.modifier)}`, // (kept) uses printModifier )) { yield replacementCandidate }