Skip to content

fix(transforms): treat scale percentage values as unitless (#216)#389

Open
YevheniiKotyrlo wants to merge 2 commits into
nativewind:mainfrom
YevheniiKotyrlo:fix/scale-percentage
Open

fix(transforms): treat scale percentage values as unitless (#216)#389
YevheniiKotyrlo wants to merge 2 commits into
nativewind:mainfrom
YevheniiKotyrlo:fix/scale-percentage

Conversation

@YevheniiKotyrlo

Copy link
Copy Markdown
Contributor

Summary

Tailwind v4 scale-* utilities crash React Native's transform validator on native:

Invariant Violation: Transform with key of "scale" must be a number: {"scale":"75%"}

CSS scale accepts percentages (75% is a 0.75 factor), but RN's transform validator only accepts unitless numbers. The "75%" string reached the validator through two independent code paths, both fixed here:

1. Compile-time (parseScaleValue) delegated unconditionally to parseLength, which formats a lightningcss { type: "percentage", value: 0.75 } back into the string "75%" — correct for layout props like width, wrong for transforms. Short-circuit percentages to return the already-normalised decimal.

2. Runtime (scale() resolver) accepted string args as valid because its guard is typeof x === "string" || typeof x === "number". This is the path Tailwind v4 actually hits — scale-75 emits scale: var(--tw-scale-x) var(--tw-scale-y) whose vars resolve to "75%" at runtime, bypassing (1). Normalize "N%"N/100 before the guards. rotate keeps "Ndeg" and translate keeps "N%" — only scale is unitless.

Fixes #216.

Reproduction (before this PR)

<View className="scale-75 h-20 w-20 bg-red-500" />

Crashes on first render with Transform with key of "scale" must be a number: {"scale":"75%"}.

Test plan

  • New edge-case tests in native/transform.test.tsx: single %, identity (100% → 1), zero (0% → 0), negative flip (-50% → -0.5), > 100% (150% → 1.5), fractional (12.5% → 0.125), per-axis (scale: 75% 50%), both var() shapes (the Tailwind emission, same and different per-axis), and a unitless-number regression guard (scale: 2 stays 2).
  • Corrected the pre-existing scale — unparsed test and the vendor Transforms - Scale tests (scale-0 / scale-x-50 / scale-y-50 / scale-50) that had encoded the buggy "N%" output (they passed while the behaviour was broken).
  • rotate / translate tests unchanged — only scale normalizes % → unitless.
  • yarn test, yarn typecheck, eslint, prettier pass. (The three babel suites fail identically on clean main in this environment — missing --experimental-vm-modules — and are unrelated.)

Tailwind v4 `scale-*` utilities crash React Native's transform validator
with `Transform with key of "scale" must be a number: {"scale":"75%"}`.
CSS `scale` accepts percentages (75% is a 0.75 factor) but RN's transform
only accepts unitless numbers, and the value reached the validator as the
string "75%" through two independent code paths -- both now fixed:

1. Compile-time (parseScaleValue): delegated unconditionally to parseLength,
   which formats a lightningcss { type: "percentage", value: 0.75 } back
   into the string "75%" (correct for layout props, wrong for transforms).
   Short-circuit percentages to return the already-normalised decimal.

2. Runtime (scale() resolver): accepted string args as valid because its
   type guard is `typeof x === "string" || "number"`. Tailwind v4 emits
   `scale: var(--tw-scale-x) var(--tw-scale-y)` whose vars resolve to "75%"
   at runtime, bypassing (1) entirely. Normalize "N%" -> N/100 before the
   guards; rotate keeps "Ndeg" and translate keeps "N%" (only scale is
   unitless).

Adds edge-case tests (identity, zero, negative, >100%, fractional, per-axis,
both var shapes, unitless-number regression) and corrects the vendor scale
tests that had encoded the buggy "N%" output.

Fixes nativewind#216
@YevheniiKotyrlo
YevheniiKotyrlo marked this pull request as ready for review July 23, 2026 14:55
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.

Animation error caused by incorrect value type

1 participant