In React Native, what do PixelRatio.getFontScale() and useWindowDimensions().fontScale report, and how should layout respond when the user enlarges text?
answer
- the system text-size setting
- 1 means the default size
- hook re-renders on fontScale change
- reflow at a threshold, never cancel it
- no fixed heights around text
basics
~20 sBoth report the user's system text-size multiplier, around 1 at the default size and higher when text is enlarged. useWindowDimensions re-renders when it changes, so layout can reflow, for example stacking a two-column ingredient row, instead of clipping.
solid answer
~40 s`fontScale` is the multiplier the platform applies to font sizes from the user's text-size setting: Settings text size on iOS, Display font size on Android. `PixelRatio.getFontScale()` returns the current value once, falling back to the pixel ratio if no font scale is reported; `useWindowDimensions().fontScale` returns the same field and re-renders the component when it changes, because the hook compares `fontScale` along with the window size. Layout should **respond**, not fight it: switch a two-column ingredient row to a single column above a threshold, replace fixed heights with `minHeight`, and let flexbox grow rows. Dividing font sizes by `fontScale` to undo the setting defeats the user's choice. On Android a new font scale is picked up when the app returns to the foreground.
code
tsx · 21 linesimport { StyleSheet, Text, View, useWindowDimensions } from 'react-native';
type Props = { quantity: string; name: string };
export function IngredientRow({ quantity, name }: Props) {
const { fontScale } = useWindowDimensions();
const stacked = fontScale >= 1.5;
return (
<View style={[styles.row, stacked && styles.stacked]}>
<Text style={styles.quantity}>{quantity}</Text>
<Text style={styles.name}>{name}</Text>
</View>
);
}
const styles = StyleSheet.create({
row: { flexDirection: 'row', gap: 12, paddingVertical: 8, minHeight: 44 },
stacked: { flexDirection: 'column', gap: 2 },
quantity: { width: 80 },
name: { flexShrink: 1 },
});go deeper
Recall that fontScale is the user's text-size multiplier and that useWindowDimensions updates when it changes.
Explain the difference between getFontScale and the hook, when each platform reports a change, and how rows should reflow.
Audit screens for fixed heights and rigid rows at large text, choose reflow thresholds, and keep re-renders contained in long lists.
Set the product's stance on large text support, which screens must reflow and how that is verified in release testing.
## What fontScale is Both platforms let users make text bigger or smaller system-wide: iOS under Display & Brightness text size (with larger sizes under Accessibility), Android under Display font size. The platform turns that setting into a **multiplier** applied to font sizes, and React Native reports it as `fontScale`: - about `1` at the default size, - above `1` when the user enlarges text, - below `1` when the user shrinks it. A `Text` with `fontSize: 16` and the default scaling behaviour renders at roughly `16 * fontScale` points. ## Two ways to read it | API | Updates your component | Notes | |---|---|---| | `PixelRatio.getFontScale()` | No, returns the value at call time | Falls back to `PixelRatio.get()` if the window reports no font scale | | `useWindowDimensions().fontScale` | Yes, re-renders when it changes | Same field of the window metrics | In the source, `getFontScale()` is `Dimensions.get('window').fontScale || PixelRatio.get()`, and `useWindowDimensions` compares `fontScale` alongside `width`, `height` and `scale`, updating state when any differ. ## When the value changes - **iOS** reports the change through its content-size notification, which React Native turns into a dimensions `change` event. - **Android** checks the configured font scale when the app resumes and emits the event if it changed, which fits the usual flow: the user leaves the app, changes the setting, and comes back. Either way, a component using `useWindowDimensions()` re-renders with the new value, and one that captured `getFontScale()` at import time keeps the stale one. ## How layout should respond The goal is a layout that **reflows** as text grows. In a recipe app the ingredient list is a row per ingredient: quantity on the left, name on the right. 1. Remove fixed heights around text. Use `minHeight` where a design needs a floor, so rows grow with their content. 2. Let text wrap: labels in rows need `flexShrink: 1` so they wrap instead of overflowing. 3. Above a threshold, change structure: at `fontScale >= 1.5`, stack quantity above name instead of side by side, because the two columns no longer fit. 4. Re-check touch targets and icons: text that grows next to fixed-size icons can crowd them. ## Choosing where to reflow Not every row needs a threshold. Use one only where the structure genuinely cannot hold enlarged text: - Rows with two text columns side by side, such as quantity and ingredient name. - Toolbars with several labelled buttons in one line. - Cards that place a title beside an image. Single-column text, lists of one-line items and paragraphs usually need nothing more than wrapping and flexible heights. Choose the threshold by testing: raise the text size one step at a time and note where the row first breaks. ## What not to do - **Cancelling the setting**: dividing every `fontSize` by `fontScale` makes the text ignore a choice the user made, often for accessibility reasons. - **Reading it once**: `getFontScale()` at module scope never sees a change the user makes while the app is running in the background. - **Using fontScale as a density**: it has nothing to do with pixels; a 3x phone at default text size still reports about 1. - **Fixing layout with smaller fonts**: shrinking text to keep a two-column layout fitting defeats the enlargement. How much individual `Text` elements may scale is a separate, per-component decision; here the concern is only reading the multiplier and making layout survive it. ## Testing - Set the largest text size on both platforms, return to the app and walk the key screens. - Check rows with long ingredient names at that size for clipping. - Confirm the threshold switch happens on return from Settings without restarting the app.
- Why does the ingredient row in a list update without the list re-rendering everything?Each row that calls `useWindowDimensions()` subscribes on its own and re-renders when `fontScale` changes. In a long list that is many re-renders at once; reading `fontScale` once in the list's parent and passing a `stacked` flag down, with memoized rows, limits the work to one decision change.
saying these in an interview costs you the question
- fontScale is the same thing as the pixel density.
- Divide font sizes by fontScale to keep the design pixel-perfect.
- PixelRatio.getFontScale() at module scope stays current.
- Fixed row heights are fine as long as the text is short.
- Text size changes require an app restart to take effect.