diff --git a/CHANGELOG.md b/CHANGELOG.md index 29a613bc0..004ace8e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fix 'Sourcemap is likely to be incorrect' warnings when using `@tailwindcss/vite` ([#20103](https://github.com/tailwindlabs/tailwindcss/pull/20103)) - Ensure `@tailwindcss/webpack` can be installed in Rspack projects without requiring `webpack` as a peer dependency ([#20027](https://github.com/tailwindlabs/tailwindcss/pull/20027)) - Canonicalization: don't suggest invalid `calc(…)` expressions (e.g. `px-[calc(1rem+0px)]` → `px-[calc(1rem+0)]`) ([#20127](https://github.com/tailwindlabs/tailwindcss/pull/20127)) +- Canonicalization: avoid suggesting large spacing-scale values for arbitrary lengths (e.g. `left-[99999px]` → `left-[99999px]`, not `left-24999.75`) ([#20130](https://github.com/tailwindlabs/tailwindcss/pull/20130)) ## [4.3.0] - 2026-05-08 diff --git a/packages/tailwindcss/src/canonicalize-candidates.test.ts b/packages/tailwindcss/src/canonicalize-candidates.test.ts index 6c568c51c..4f0160bb3 100644 --- a/packages/tailwindcss/src/canonicalize-candidates.test.ts +++ b/packages/tailwindcss/src/canonicalize-candidates.test.ts @@ -871,6 +871,15 @@ describe.each([['default'], ['with-variant'], ['important'], ['prefix']])('%s', ['[margin:-0]', 'm-0'], ['[margin:0px]', 'm-0'], + // Limit conversions for big values using the `--spacing` multiplier + ['left-[99999px]', 'left-[99999px]'], // This would otherwise result in `left-24999.75` + ['left-[-99999px]', 'left-[-99999px]'], // This would otherwise result in `-left-24999.75` + ['left-[96rem]', 'left-384'], // Within the limit + ['left-[-96rem]', '-left-384'], // Within the limit + ['left-[calc(96rem+1px)]', 'left-[calc(96rem+1px)]'], // Out of the positive limit + ['left-[calc(-96rem-1px)]', 'left-[calc(-96rem-1px)]'], // Out of the negative limit + ['z-[9999999]', 'z-9999999'], // `--spacing` multiplier is not used + // Not a length-unit, can't safely constant fold ['[margin:0%]', 'm-[0%]'], diff --git a/packages/tailwindcss/src/canonicalize-candidates.ts b/packages/tailwindcss/src/canonicalize-candidates.ts index 3922721b4..6faf346ed 100644 --- a/packages/tailwindcss/src/canonicalize-candidates.ts +++ b/packages/tailwindcss/src/canonicalize-candidates.ts @@ -1074,6 +1074,35 @@ function printUnprefixedCandidate(designSystem: DesignSystem, candidate: Candida // ---- const SPACING_KEY = Symbol() + +// We prefer bare values over arbitrary values such as `left-[6px]` → +// `left-1.5`. +// +// There are situations where this doesn't always make sense. E.g.: +// `left-[99999px]` → `left-24999.75`. +// +// We could try and analyze the incoming value and bail out if we detect certain +// patterns. But a first step would be to limit the bare values for large values +// and keep them low. The largest rem value we have is for the 2xl breakpoint, +// which is 96rem, which is 1536px. +const MAX_BARE_VALUE_IN_PX = 1536 +const MAX_BARE_VALUE_IN_REM = MAX_BARE_VALUE_IN_PX / 16 + +function isReasonableBareValue(value: number, designSystem: DesignSystem, rem: number | null) { + let spacingMultiplier = designSystem.resolveThemeValue('--spacing') + if (spacingMultiplier === undefined) return false + + let parsed = dimensions.get(constantFoldDeclaration(spacingMultiplier, rem)) + if (parsed === null) return false + + let [spacingValue, spacingUnit] = parsed + let bareValueInPixels = value * spacingValue + + if (spacingUnit === 'px') return bareValueInPixels <= MAX_BARE_VALUE_IN_PX + if (spacingUnit === 'rem') return bareValueInPixels <= MAX_BARE_VALUE_IN_REM + return false +} + function createSpacingCache( designSystem: DesignSystem, options?: CanonicalizeOptions, @@ -1220,7 +1249,10 @@ function arbitraryUtilities(candidate: Candidate, options: InternalCanonicalizeO ) { let bareValue = designSystem.storage[SPACING_KEY]?.get(value) ?? null if (bareValue !== null) { - if (isValidSpacingMultiplier(bareValue)) { + if ( + isValidSpacingMultiplier(bareValue) && + isReasonableBareValue(bareValue, designSystem, options.signatureOptions.rem) + ) { yield Object.assign({}, candidate, { value: { kind: 'named', value: bareValue, fraction: null }, }) @@ -1261,7 +1293,11 @@ function arbitraryUtilities(candidate: Candidate, options: InternalCanonicalizeO // Try bare value based on the `--spacing` value. E.g.: // // - `w-[64rem]` → `w-256` - if (spacingMultiplier !== null) { + if ( + spacingMultiplier !== null && + isValidSpacingMultiplier(spacingMultiplier) && + isReasonableBareValue(spacingMultiplier, designSystem, options.signatureOptions.rem) + ) { for (let replacementCandidate of parseCandidate( designSystem, `${root}-${spacingMultiplier}`,