Skip to content

Conversation

@RobinMalfait
Copy link
Member

This PR adds an improvement to our canonicalization logic when dealing with arbitrary values. When trying to canonicalize utilities, we make use of the intellisense suggestions list where we typically use multiples of the spacing scale.

This means that a value like gap-[128px] gets properly canonicalized to gap-32. However, when you try a value that we typically don't suggest such as gap-[116px] then it doesn't get canonicalized at all.

This PR fixes that by trying to use the spacing scale and convert 116px / 4px and try the gap-29 utility instead.

This is done by canonicalizing the incoming arbitrary value and the spacing multipliers such that --spacing: 0.25rem and --spacing: 4px both work as expected.

Test plan

  1. Added some tests with a spacing scale of 0.25rem (which is the default)
  2. Added some tests with the same spacing scale in a different unit 4px
  3. Added some tests with a different spacing scale 1px

Also had to update 1 test that now gets canonicalized properly, e.g.: w-[124px]w-31.

@RobinMalfait RobinMalfait requested a review from a team as a code owner November 26, 2025 10:45
@coderabbitai
Copy link

coderabbitai bot commented Nov 26, 2025

Walkthrough

Adds a new canonicalization path for arbitrary utility values in Tailwind CSS: when rem-based canonicalization is enabled and the candidate is a functional arbitrary value, the code resolves the spacing multiplier from the design system, constant-folds it, canonicalizes the candidate value, compares dimensions, and — if units match and the multiplier is non-zero — computes a bare numeric value and emits a named candidate. This path runs before the existing spacing-map lookup. Test coverage was expanded with cases for default and custom spacing scales and mixed units, and some expected outcomes were updated.

Pre-merge checks

✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: improving canonicalization of arbitrary utility values to bare values, which is exactly what the changeset implements.
Description check ✅ Passed The description is directly related to the changeset, explaining the improvement to canonicalization logic, the specific problem being solved, and the test plan with concrete examples.

Comment @coderabbitai help to get the list of available commands and usage tips.

To do this, we check if the `rem` option is provided, then we
canonicalize the incoming value. We also canonicalize the spacing
multiplier so we can deal with both `--spacing: 0.25rem` as `--spacing:
4px` values (different units).

We then compute the bare value, and just try it as a replacement.
@RobinMalfait RobinMalfait force-pushed the feat/canonicalize-arbitrary-to-bare-values branch from 25b0b5f to 4bd24e9 Compare November 26, 2025 10:46
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
CHANGELOG.md (1)

20-20: Polish wording + add example; consider section placement

Prefer Tailwind terminology (“arbitrary values in utilities”) and include an example for clarity. Optionally, this reads more like a behavior change than a bug fix, so consider moving to “Changed” if that better matches intent.

- - Try to canonicalize any arbitrary utility to a bare value ([#19379](https://github.com/tailwindlabs/tailwindcss/pull/19379))
+ - Canonicalize arbitrary values in utilities to bare values (e.g., `gap-[116px]` → `gap-29`) ([#19379](https://github.com/tailwindlabs/tailwindcss/pull/19379))
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 117433a and 4bd24e9.

📒 Files selected for processing (3)
  • CHANGELOG.md (1 hunks)
  • packages/tailwindcss/src/canonicalize-candidates.test.ts (1 hunks)
  • packages/tailwindcss/src/canonicalize-candidates.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
packages/tailwindcss/src/canonicalize-candidates.ts (2)
packages/tailwindcss/src/constant-fold-declaration.ts (1)
  • constantFoldDeclaration (8-115)
packages/tailwindcss/src/utils/dimensions.ts (1)
  • dimensions (6-20)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Linux
  • GitHub Check: Linux / upgrade
🔇 Additional comments (2)
packages/tailwindcss/src/canonicalize-candidates.test.ts (2)

533-547: Excellent test coverage for the new canonicalization logic.

The new test cases comprehensively verify that arbitrary values can be canonicalized to bare values across different spacing scales and units:

  • Default scale (0.25rem/4px): gap-[116px]gap-29
  • Custom scale (1px): gap-[116px]gap-116
  • Equivalent forms: rem, calc, and px all canonicalize to the same result ✓

The math checks out: 116px ÷ 4px = 29, and the tests verify this works whether the spacing is expressed as 0.25rem or 4px.


550-550: Correctly updated expectation.

The updated expectation w-[124px]w-31 correctly reflects the new canonicalization behavior (124px ÷ 4px = 31).

Probably will never happen in the real world because a spacing
multiplier of 0px (or equivalent) doesn't make sense.
@RobinMalfait RobinMalfait enabled auto-merge (squash) November 26, 2025 11:04
@RobinMalfait RobinMalfait merged commit 9e436f7 into main Nov 26, 2025
7 checks passed
@RobinMalfait RobinMalfait deleted the feat/canonicalize-arbitrary-to-bare-values branch November 26, 2025 11:07
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
packages/tailwindcss/src/canonicalize-candidates.ts (1)

993-1025: Spacing canonicalization block looks correct; consider simplifying constantFold usage.

The new rem‑aware spacing canonicalization logic is well constrained (only for functional + arbitrary values, unit‑matched, and guarded by signature comparison), and the division‑by‑zero check avoids pathological configs cleanly.

One small nit: constantFoldDeclaration currently returns a string, so the canonicalizedSpacingMultiplier !== null check is a no‑op. You could drop that check and slightly flatten the nesting by computing both folded values up front and early‑returning when dimensions.get(...) fails, e.g.:

-          let canonicalizedSpacingMultiplier = constantFoldDeclaration(
-            spacingMultiplier,
-            options.signatureOptions.rem,
-          )
-          if (canonicalizedSpacingMultiplier !== null) {
-            let canonicalizedValue = constantFoldDeclaration(value, options.signatureOptions.rem)
-            let valueDimension = dimensions.get(canonicalizedValue)
-            let spacingMultiplierDimension = dimensions.get(canonicalizedSpacingMultiplier)
-            if (
-              valueDimension &&
-              spacingMultiplierDimension &&
-              valueDimension[1] === spacingMultiplierDimension[1] && // Ensure the units match
-              spacingMultiplierDimension[0] !== 0
-            ) {
-              let bareValue = `${valueDimension[0] / spacingMultiplierDimension[0]}`
-              if (isValidSpacingMultiplier(bareValue)) {
-                yield Object.assign({}, candidate, {
-                  value: { kind: 'named', value: bareValue, fraction: null },
-                })
-              }
-            }
-          }
+          let canonicalizedSpacingMultiplier = constantFoldDeclaration(
+            spacingMultiplier,
+            options.signatureOptions.rem,
+          )
+          let canonicalizedValue = constantFoldDeclaration(value, options.signatureOptions.rem)
+
+          let valueDimension = dimensions.get(canonicalizedValue)
+          let spacingMultiplierDimension = dimensions.get(canonicalizedSpacingMultiplier)
+          if (
+            valueDimension &&
+            spacingMultiplierDimension &&
+            valueDimension[1] === spacingMultiplierDimension[1] && // Ensure the units match
+            spacingMultiplierDimension[0] !== 0
+          ) {
+            let bareValue = `${valueDimension[0] / spacingMultiplierDimension[0]}`
+            if (isValidSpacingMultiplier(bareValue)) {
+              yield Object.assign({}, candidate, {
+                value: { kind: 'named', value: bareValue, fraction: null },
+              })
+            }
+          }

Purely a readability/maintenance tweak; behavior stays the same.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4bd24e9 and 1380b87.

📒 Files selected for processing (1)
  • packages/tailwindcss/src/canonicalize-candidates.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
packages/tailwindcss/src/canonicalize-candidates.ts (3)
packages/tailwindcss/src/constant-fold-declaration.ts (1)
  • constantFoldDeclaration (8-115)
packages/tailwindcss/src/utils/dimensions.ts (1)
  • dimensions (6-20)
packages/tailwindcss/src/utils/infer-data-type.ts (1)
  • isValidSpacingMultiplier (356-358)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants