Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 31 additions & 9 deletions packages/tailwindcss/src/canonicalize-candidates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -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
}
}
}

Expand All @@ -1043,15 +1064,16 @@ 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
}

// Try as arbitrary value with modifier
if (candidate.modifier) {
for (let replacementCandidate of parseCandidate(
designSystem,
`${root}-[${value}]${printModifier(candidate.modifier)}`,
`${root}-[${valueStr}]${printModifier(candidate.modifier)}`, // (kept) uses printModifier
)) {
yield replacementCandidate
}
Expand Down