DEV Community

Cover image for Stop scaling fonts by hand: responsive React Native that survives rotation
Aqeel Ahmad
Aqeel Ahmad

Posted on AI-assisted

Stop scaling fonts by hand: responsive React Native that survives rotation

Most responsive helpers for React Native work out their numbers once, when the app starts. Rotate a tablet, open split screen or unfold a phone, and the layout keeps the old numbers. I wanted a library that reads the real window every time, so I built one and just released version 1.0.0.

Fixed pixels versus scaled layout on a small phone, a phone and a tablet

The problem with scaling once

The popular pattern is a helper that divides the screen width by your design width. It works on a phone in portrait. It gets awkward when the window changes, because the value was calculated at import time and never updates.

Version 1.0.0 of react-native-layout-transfarmer takes a different approach. The functions read the window when you call them, and a hook re renders your component when the window changes.

One screen scaling while the window width changes from 320 to 760

Install and configure once

npm install react-native-layout-transfarmer
Enter fullscreen mode Exit fullscreen mode
import { configureLayout } from 'react-native-layout-transfarmer';

// The size of your Figma frame
configureLayout({ designWidth: 375, designHeight: 812, maxScale: 1.5 });
Enter fullscreen mode Exit fullscreen mode

maxScale is how you keep a 13 inch iPad from drawing buttons three times too large.

Write styles at design size

import { Text, View } from 'react-native';
import { useScaledStyles } from 'react-native-layout-transfarmer';

const designStyles = {
  card: { width: 335, padding: 16, borderRadius: 12, backgroundColor: '#4F46E5' },
  title: { fontSize: 24, color: '#fff' },
};

export function Card() {
  const styles = useScaledStyles(designStyles);
  return (
    <View style={styles.card}>
      <Text style={styles.title}>Looks right on every device</Text>
    </View>
  );
}
Enter fullscreen mode Exit fullscreen mode

Widths use the horizontal scale, heights use the vertical scale and text uses a moderate scale that you can tie to the operating system font size. Values such as flex, opacity and borderWidth are left alone. The styles are memoized and update on rotation and window resize.

If you only need single values, the same names as react-native-size-matters are available, which makes migration a find and replace.

import { scale, verticalScale, moderateScale, scaleFont } from 'react-native-layout-transfarmer';
Enter fullscreen mode Exit fullscreen mode

Control the system font size

configureLayout({ fontScale: 'capped', maxFontScale: 1.3 });
const fontSize = scaleFont(16);
Enter fullscreen mode Exit fullscreen mode

respect leaves the setting to Text, ignore cancels it and capped allows it up to a limit. That keeps accessibility while protecting layouts that cannot grow forever.

Does it really match across devices

I wanted proof, not a claim. The example app has a Parity screen. It draws one 375 by 600 board with emoji at fixed design points, and each device prints where every emoji landed as a percentage of the board.

The same board on an iPhone, an iPad, an Android phone and an Android tablet

On an iPhone 17e simulator, an iPad Pro 13 simulator and an Android phone emulator, the target emoji sits at 49.9 percent across, 50.0 percent down, at 17.1 percent of the board width. The other six emoji match to the same digit. I also resized the running Android emulator to tablet and landscape sizes, and the app laid itself out again without a reload.

Bonus for editor apps

If you build sticker, story or template editors, you save objects at one size and draw them at another. The library includes a viewport transform with stretch, contain and cover fits, rotation and zoom around the center, and an inverse so a drag can be saved back in design units.

const t = useCanvasTransform({ width: 1080, height: 1920 }, box, { fit: 'contain', anchor: 'center' });
const placed = t.objects(stickers);
const saved = t.inverse.object(draggedSticker);
Enter fullscreen mode Exit fullscreen mode

Stickers drawn with contain, cover and stretch while the window resizes

Try it

It is pure JavaScript with zero dependencies, about 4 kB gzipped, with full TypeScript types, and it runs in Expo Go. If something is missing, open a discussion on GitHub. I answer every one.

Top comments (0)