Skip to content

Commit c586bd6

Browse files
authored
Canonicalize calc(var(--spacing)*…) expressions into --spacing(…) (tailwindlabs#19769)
This PR canonicalizes usages of `calc(var(--spacing)*…)` to `--spacing(…)` when used in arbitrary values. Some examples: | Before | After | | --- | --- | | `pt-[min(20%,calc(var(--spacing)*8))]` | `pt-[min(20%,--spacing(8))]` | | `pt-[min(20%,calc(var(--spacing)*var(--other)))]` | `pt-[min(20%,--spacing(var(--other)))]` | | `pt-[calc(var(--spacing)*8)]` | `pt-8` | | `pt-[calc(var(--spacing)*var(--other))]` | `pt-[--spacing(var(--other))]` | | `[padding-top:min(20%,calc(var(--spacing)*8))]` | `pt-[min(20%,--spacing(8))]` | | `[padding-top:min(20%,calc(var(--spacing)*var(--other)))]` | `pt-[min(20%,--spacing(var(--other)))]` | | `[padding-top:calc(var(--spacing)*8)]` | `pt-8` | | `[padding-top:calc(var(--spacing)*var(--other))]` | `pt-[--spacing(var(--other))]` | ## Test plan 1. Existing tests pass 2. Added new tests
1 parent bf2e2fe commit c586bd6

3 files changed

Lines changed: 79 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1414
### Fixed
1515

1616
- Guard object lookups against inherited prototype properties ([#19725](https://github.com/tailwindlabs/tailwindcss/pull/19725))
17+
- Canonicalize `calc(var(--spacing)*…)` expressions into `--spacing(…)` ([#19769](https://github.com/tailwindlabs/tailwindcss/pull/19769))
1718

1819
## [4.2.1] - 2026-02-23
1920

packages/tailwindcss/src/canonicalize-candidates.test.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,20 @@ describe.each([['default'], ['with-variant'], ['important'], ['prefix']])('%s',
254254
// Additionally we remove unnecessary whitespace.
255255
['grid-cols-[min(50%_,_theme(spacing.80))_auto]', 'grid-cols-[min(50%,--spacing(80))_auto]'],
256256

257+
// `calc(var(--spacing)*…)` to `--spacing(…)`
258+
['pt-[min(20%,calc(var(--spacing)*8))]', 'pt-[min(20%,--spacing(8))]'],
259+
['pt-[min(20%,calc(var(--spacing)*var(--other)))]', 'pt-[min(20%,--spacing(var(--other)))]'],
260+
['pt-[calc(var(--spacing)*8)]', 'pt-8'],
261+
['pt-[calc(var(--spacing)*var(--other))]', 'pt-[--spacing(var(--other))]'],
262+
263+
['[padding-top:min(20%,calc(var(--spacing)*8))]', 'pt-[min(20%,--spacing(8))]'],
264+
[
265+
'[padding-top:min(20%,calc(var(--spacing)*var(--other)))]',
266+
'pt-[min(20%,--spacing(var(--other)))]',
267+
],
268+
['[padding-top:calc(var(--spacing)*8)]', 'pt-8'],
269+
['[padding-top:calc(var(--spacing)*var(--other))]', 'pt-[--spacing(var(--other))]'],
270+
257271
// `theme(…)` calls valid in v3, but not in v4 should still be converted.
258272
['[--foo:theme(transitionDuration.500)]', '[--foo:theme(transitionDuration.500)]'],
259273

packages/tailwindcss/src/canonicalize-candidates.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,7 @@ type UtilityCanonicalizationFunction = (
527527
const UTILITY_CANONICALIZATIONS: UtilityCanonicalizationFunction[] = [
528528
bgGradientToLinear,
529529
themeToVarUtility,
530+
calcToSpacingFunction,
530531
arbitraryUtilities,
531532
bareValueUtilities,
532533
deprecatedUtilities,
@@ -640,6 +641,69 @@ function themeToVarVariant(
640641
return variant
641642
}
642643

644+
function calcToSpacingFunction(
645+
candidate: Candidate,
646+
options: InternalCanonicalizeOptions,
647+
): Candidate {
648+
if (candidate.kind === 'arbitrary') {
649+
candidate.value = spacingCalcToSpacingFunction(candidate.value, options.designSystem)
650+
} else if (candidate.kind === 'functional' && candidate.value?.kind === 'arbitrary') {
651+
candidate.value.value = spacingCalcToSpacingFunction(
652+
candidate.value.value,
653+
options.designSystem,
654+
)
655+
}
656+
657+
return candidate
658+
}
659+
660+
function spacingCalcToSpacingFunction(input: string, designSystem: DesignSystem) {
661+
let spacingVariable = designSystem.theme.prefix
662+
? `--${designSystem.theme.prefix}-spacing`
663+
: '--spacing'
664+
665+
let ast = ValueParser.parse(input)
666+
667+
walk(ast, (node) => {
668+
// calc(…)
669+
if (node.kind !== 'function' || node.value !== 'calc') return
670+
671+
// calc(var(--spacing) * 2)
672+
// -------------- 1. function (var)
673+
// - 2. separator ( )
674+
// - 3. word (*)
675+
// - 4. separator ( )
676+
// - 5. any value, word (2)
677+
if (node.nodes.length !== 5) return
678+
679+
// *
680+
if (node.nodes[2].kind !== 'word' || node.nodes[2].value !== '*') {
681+
return
682+
}
683+
684+
// var(--spacing)
685+
if (
686+
node.nodes[0].kind !== 'function' ||
687+
node.nodes[0].value !== 'var' ||
688+
node.nodes[0].nodes.length !== 1 ||
689+
node.nodes[0].nodes[0].kind !== 'word' ||
690+
node.nodes[0].nodes[0].value !== spacingVariable
691+
) {
692+
return
693+
}
694+
695+
return WalkAction.Replace(
696+
ValueParser.parse(
697+
`--spacing(${
698+
ValueParser.toCss([node.nodes[4]]) // Value node
699+
})`,
700+
),
701+
)
702+
})
703+
704+
return ValueParser.toCss(ast)
705+
}
706+
643707
const CONVERTER_KEY = Symbol()
644708
function createConverterCache(
645709
designSystem: DesignSystem,

0 commit comments

Comments
 (0)