Summary
On React Native, <Heading> and <Paragraph> render invisible text — the
element occupies its layout height, but no glyphs are visible. The cause is a
unitless lineHeight value that is valid as a CSS ratio on web but is
interpreted as absolute pixels by React Native.
Environment
usemotif / @usemotif/react-native 1.0.0
- Expo SDK 54, React Native 0.81.5, React 19.1, Hermes
- iOS (iPhone 16 Pro simulator) — but the cause is platform-agnostic RN behaviour
- Runtime path only (no compiler plugin)
Symptom
import { Heading, Text } from 'usemotif';
// Renders blank space — the heading is invisible.
<Heading level={4} color="$colors.text.default">Hello</Heading>
// Renders fine.
<Text fontSize="$xl" fontWeight="$bold" color="$colors.text.default">Hello</Text>
The <Heading> reserves vertical space but shows nothing, which reads as a
mystery gap above the next element.
Root cause
packages/react-native/src/typography.tsx:
export function Heading({
level = 2,
children,
...rest
}: HeadingProps): ReactElement {
return (
<Text
fontSize={headingSize[level - 1]!}
fontWeight="$bold"
lineHeight={1.2} // <-- unitless: a web line-height ratio
accessibilityRole="header"
{...rest}
>
{children}
</Text>
);
}
Paragraph has the same issue with lineHeight={1.6}.
On the web, line-height: 1.2 is a unitless multiplier (1.2 × font-size). On
React Native, lineHeight is an absolute value in DIPs — there is no
unitless form. So the native Heading sets a line box 1.2px tall and clips
the glyphs to nothing. Text works because it sets no lineHeight at all.
This contradicts the file's own header comment, which states the native
primitives use "the same defaults as the web implementations so cross-platform
code looks the same" — the value is the same but its meaning is not.
Reproduction
- Fresh Expo app, install
usemotif + @usemotif/tokens.
- Mount
<ThemeProvider> and render <Heading level={4}>Hello</Heading>.
- The heading is invisible; an equivalent
<Text> is not.
Confirmed workaround
Pass an explicit pixel lineHeight — {...rest} spreads after the hard-coded
default, so it overrides:
<Heading level={4} lineHeight={26}>
Hello
</Heading> // visible
Suggested fix
In the native typography primitives, resolve a unitless lineHeight against the
resolved fontSize (lineHeight × fontSize → px) before passing it to RN —
either inside Heading/Paragraph, or generically in the native style resolver
so any consumer passing a unitless lineHeight (a natural web habit) gets
correct cross-platform behaviour. The latter keeps the "cross-platform code
looks the same" promise intact.
Summary
On React Native,
<Heading>and<Paragraph>render invisible text — theelement occupies its layout height, but no glyphs are visible. The cause is a
unitless
lineHeightvalue that is valid as a CSS ratio on web but isinterpreted as absolute pixels by React Native.
Environment
usemotif/@usemotif/react-native1.0.0Symptom
The
<Heading>reserves vertical space but shows nothing, which reads as amystery gap above the next element.
Root cause
packages/react-native/src/typography.tsx:Paragraphhas the same issue withlineHeight={1.6}.On the web,
line-height: 1.2is a unitless multiplier (1.2 × font-size). OnReact Native,
lineHeightis an absolute value in DIPs — there is nounitless form. So the native
Headingsets a line box1.2pxtall and clipsthe glyphs to nothing.
Textworks because it sets nolineHeightat all.This contradicts the file's own header comment, which states the native
primitives use "the same defaults as the web implementations so cross-platform
code looks the same" — the value is the same but its meaning is not.
Reproduction
usemotif+@usemotif/tokens.<ThemeProvider>and render<Heading level={4}>Hello</Heading>.<Text>is not.Confirmed workaround
Pass an explicit pixel
lineHeight—{...rest}spreads after the hard-codeddefault, so it overrides:
Suggested fix
In the native typography primitives, resolve a unitless
lineHeightagainst theresolved
fontSize(lineHeight × fontSize → px) before passing it to RN —either inside
Heading/Paragraph, or generically in the native style resolverso any consumer passing a unitless
lineHeight(a natural web habit) getscorrect cross-platform behaviour. The latter keeps the "cross-platform code
looks the same" promise intact.