From a870a319f1f143ac51603ac3a356e935b6fea0c6 Mon Sep 17 00:00:00 2001 From: Dan Stepanov Date: Tue, 31 Mar 2026 19:18:06 -0300 Subject: [PATCH] fix: use :root font-size as compile-time rem base When no explicit inlineRem option is passed, the compiler now scans CSS for :root { font-size: Npx } and uses that value as the rem multiplier instead of hardcoding 14. This makes the documented v5 approach for overriding rem actually work. Closes nativewind/nativewind#1650 --- src/__tests__/native/units.test.tsx | 22 ++++++++++++++++++++++ src/compiler/compiler.ts | 17 ++++++++++++++--- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/src/__tests__/native/units.test.tsx b/src/__tests__/native/units.test.tsx index 91195fd2..ebd9fc37 100644 --- a/src/__tests__/native/units.test.tsx +++ b/src/__tests__/native/units.test.tsx @@ -126,6 +126,28 @@ test("rem - inline override", () => { }); }); +test("rem - css root font-size override", () => { + registerCSS(` + :root { font-size: 16px; } + .my-class { font-size: 10rem; } + `); + + const { result } = renderHook(() => { + return useNativeCss(View, { className: "my-class" }); + }); + + expect(result.current.type).toBe(VariableContext.Provider); + expect(result.current.props.value).toStrictEqual({ + [VAR_SYMBOL]: true, + [emVariableName]: 160, + }); + + expect(result.current.props.children.type).toBe(View); + expect(result.current.props.children.props).toStrictEqual({ + style: { fontSize: 160 }, + }); +}); + test("rem - css override", () => { registerCSS( ` diff --git a/src/compiler/compiler.ts b/src/compiler/compiler.ts index 52c4b431..8245e64b 100644 --- a/src/compiler/compiler.ts +++ b/src/compiler/compiler.ts @@ -84,17 +84,28 @@ export function compile(code: Buffer | string, options: CompilerOptions = {}) { const vars = new Map(); + // Determine the effective rem multiplier for compile-time inlining. + // If inlineRem is explicitly set, use that. Otherwise, scan for + // :root { font-size: Npx } in the CSS to allow CSS-based configuration. + let effectiveRem: number | false = options.inlineRem ?? undefined!; + if (effectiveRem === undefined) { + const css = typeof code === "string" ? code : code.toString(); + const match = css.match(/:root\s*\{[^}]*font-size:\s*([\d.]+)px/); + effectiveRem = match?.[1] ? parseFloat(match[1]) : 14; + } + const firstPassVisitor: Visitor = {}; - if (options.inlineRem !== false) { + if (effectiveRem !== false) { + const remMultiplier = effectiveRem; firstPassVisitor.Length = (length) => { - if (length.unit !== "rem" || options.inlineRem === false) { + if (length.unit !== "rem") { return length; } return { unit: "px", - value: round(length.value * (options.inlineRem ?? 14)), + value: round(length.value * remMultiplier), }; }; }