Skip to content

fix: propagate inlineRem and Metro config to compiler (Tailwind v4) (#316)#336

Open
maxencehenneron wants to merge 2 commits intonativewind:mainfrom
maxencehenneron:fix/tailwind-v4-rem-propagation
Open

fix: propagate inlineRem and Metro config to compiler (Tailwind v4) (#316)#336
maxencehenneron wants to merge 2 commits intonativewind:mainfrom
maxencehenneron:fix/tailwind-v4-rem-propagation

Conversation

@maxencehenneron
Copy link
Copy Markdown
Contributor

@maxencehenneron maxencehenneron commented Apr 20, 2026

Summary

Fixes two related bugs that cause Tailwind v4's text utilities to render at the wrong size on native. Both manifest specifically on v4 and are benign on v3 — v3 emits direct rem values while v4 routes them through CSS custom properties, which traverse a different code path in the compiler.

Bug 1 — Metro transformer drops withReactNativeCSS options

withReactNativeCSS() writes its options to config.transformer.reactNativeCSS, which Metro surfaces as config.reactNativeCSS inside the transformer. The transformer was instead spreading options.reactNativeCSS — a key that nothing ever writes to. JsTransformOptions is per-request context built by Metro's server (platform, dev, hot, etc.); config.transformer.* never flows into it.

Result: every option passed to withReactNativeCSS (including inlineRem) was silently dropped before reaching compile().

Bug 2 — regex-discovered rem isn't propagated downstream

compile() has a fallback that scans CSS for :root { font-size: Npx } when inlineRem isn't explicitly set, so users can configure rem scaling from CSS. The discovered value was only wired into the first-pass lightningcss Length visitor via a local effectiveRem — never written back into options. The second code path — parseLength() in declarations.ts, which reads inlineRem from builder.getOptions() — kept using the default 14.

Why v3 is unaffected

Tailwind v3 emits rem values directly:

css .text-base { font-size: 1rem; line-height: 1.5rem; } ​

The 1rem is a proper lightningcss Length node, so the first-pass visitor handles it and scales correctly. The broken parseLength path is never exercised.

Why v4 breaks

Tailwind v4 routes utility values through CSS custom properties:

css :root { --text-base: 1rem; } .text-base { font-size: var(--text-base); } ​

The 1rem inside the custom property is a token-list, inlined verbatim by inlineVariables, then resolved via parseLength in the second pass — where it reads the stale default.

see

const { inlineRem = 14 } = builder.getOptions();
if (typeof length === "number") {
return round(length);
}

Net effect on v4: every text-* utility was sized against rem=14 regardless of what :root { font-size } declared or what the user configured. Meanwhile direct rules like .my-class { font-size: 1.5rem } scaled correctly — which is why the bug evaded isolated tests.

Two related bugs caused Tailwind v4's text utilities to render at the
wrong size on native. Both manifest in v4 specifically and are benign
on v3, because v3 emits direct `rem` values while v4 routes them
through CSS custom properties.

Bug 1 — Metro transformer drops `withReactNativeCSS` options
-----------------------------------------------------------
`withReactNativeCSS()` writes its options to
`config.transformer.reactNativeCSS`, which Metro surfaces as
`config.reactNativeCSS` in the transformer. The transformer was
instead spreading `options.reactNativeCSS`, a key that nothing ever
writes to — `JsTransformOptions` is per-request context built by
Metro, and `config.transformer.*` never flows into it.

Result: every option passed to `withReactNativeCSS` (including
`inlineRem`) was silently dropped before reaching `compile()`.

Bug 2 — regex-discovered rem isn't propagated downstream
--------------------------------------------------------
`compile()` contains a fallback that scans the CSS for
`:root { font-size: Npx }` when `inlineRem` isn't explicitly set,
so users can configure rem scaling directly from CSS. The discovered
value was only wired into the first-pass lightningcss `Length`
visitor via a local `effectiveRem`. It was never written back into
`options`, so the second code path — `parseLength()` in
`declarations.ts`, which reads `inlineRem` from
`builder.getOptions()` — kept using the default 14.

This is invisible on Tailwind v3, which emits
`.text-base { font-size: 1rem }` directly: the `1rem` is a proper
Length node, so the first-pass visitor handles it and scales
correctly. It breaks on Tailwind v4, which emits
`:root { --text-base: 1rem }` + `.text-base { font-size: var(--text-base) }`.
The `1rem` inside the custom property is a token-list, inlined
verbatim by `inlineVariables`, then resolved via `parseLength` in
the second pass — where it reads the stale default.

Net effect on v4: every `text-*` utility was sized against rem=14
regardless of what `:root { font-size }` declared or what the user
configured, while direct `.my-class { font-size: 1.5rem }` rules
scaled correctly. The divergence is why the bug evaded isolated tests.

The fix
-------
- metro-transformer.ts: read `reactNativeCSS` from `config`, not
  `options`. Drop the unreachable `options.reactNativeCSS` spread
  and its type intersection.
- compiler.ts: after resolving `effectiveRem`, write it back to
  `options.inlineRem` so the builder's downstream consumers see
  the same value as the first-pass visitor.
Copilot AI review requested due to automatic review settings April 20, 2026 21:08
@maxencehenneron
Copy link
Copy Markdown
Contributor Author

Fixes (#316) for tailwind v4.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes Tailwind v4 rem-scaling issues on React Native by ensuring compiler configuration (notably inlineRem) actually reaches all compilation paths, including the second-pass length parsing used after CSS variable inlining.

Changes:

  • Fix Metro transformer to read withReactNativeCSS() options from config.reactNativeCSS rather than per-request transform options.
  • Persist the regex-discovered :root { font-size: Npx } rem value back onto options.inlineRem so the second-pass parseLength() path uses the same effective rem multiplier.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
src/metro/metro-transformer.ts Propagates compiler options from Metro config into compile() so withReactNativeCSS() options aren’t dropped.
src/compiler/compiler.ts Writes discovered effective rem back into compiler options to keep first-pass and second-pass rem handling consistent.

Comment thread src/compiler/compiler.ts
@maxencehenneron maxencehenneron force-pushed the fix/tailwind-v4-rem-propagation branch from 7444258 to 46ef4c4 Compare April 20, 2026 21:41
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.

2 participants