---
name: react-native-nested-text-layout
description: >-
  Fixes broken line height, spacing, wrapping, and baselines when React Native
  copy is built from one Text (or Animated.Text) per character or word inside
  View rows and flexWrap. Use when debugging "wrong padding/margins/typography"
  on staggered or per-glyph text, typewriter effects, character fade-ins, or
  any pattern that maps strings to many sibling Text nodes inside flex
  containers.
---

# React Native: nested Text for inline / paragraph layout

## When to suspect this issue

Symptoms often look like font or theme bugs but are really **layout mode**:

- Line height or vertical rhythm looks wrong vs the same styles on a single `Text`
- Wrapped lines have uneven gaps, odd baselines, or "extra" space above/below
- `letterSpacing` or transforms look different from a normal paragraph
- Tweaking `lineHeight`, `padding`, or `margin` helps only a little or not at all

**Common trigger code:** each glyph is its own `Text` / `Animated.Text` wrapped in `View` with `flexDirection: "row"` and `flexWrap: "wrap"` (sometimes one `View` per word).

That structure is **flex layout of boxes**, not the **text layout engine**. Breaks, baselines, and multi-line spacing will not match a real paragraph.

## Fix: one parent `Text`, nested children

Put **one outer** `Text` (or `Animated.Text` if the whole block is animated) around the fragments. Nested `Text` / `Animated.Text` children lay out **inline**, share wrapping, and match normal typography.

- Apply **paragraph styles** (`fontSize`, `lineHeight`, `fontFamily`, `color`, `letterSpacing`, `textTransform`, etc.) on the **parent** when possible; children inherit unless overridden.
- Each animated character stays an **`Animated.Text`** child **without** duplicating full paragraph styles if inheritance is enough.

### Do not use `Fragment` inside `Text`

React Native `Text` children must be `Text` nodes or strings. Build arrays with **`flatMap`** / spread instead of `<>...</>`.

### Example shape (character stagger)

```tsx
<Text style={paragraphStyle}>
  {words.flatMap((word, wordIndex) => {
    const keys = word.split("").map((char, i) => (
      <Animated.Text key={`${wordIndex}-${i}`} entering={/* ... */}>
        {char}
      </Animated.Text>
    ));
    if (wordIndex < words.length - 1) {
      return [
        ...keys,
        <Animated.Text key={`sp-${wordIndex}`} entering={/* ... */}>
          {"\u00A0"}
        </Animated.Text>,
      ];
    }
    return keys;
  })}
</Text>
```

Words can be precomputed with `useMemo` if needed; keep keys stable.

## Related gotchas

- **Android:** `includeFontPadding` on the **parent** `Text` can tighten vertical metrics if labels still look vertically padded.
- **Layout reservation:** If a hidden placeholder and visible control differ in **copy or line count** (e.g. two button labels), height can shift when swapping; use the **same final string** (or explicit `minHeight`) for the placeholder.
- **Still wrong?** After nested `Text`, if top inset looks off, check **navigator header + `SafeAreaView`** (e.g. double top inset with a transparent stack header), not typography.

## Quick checklist

1. Is per-character (or per-word-for-wrap) content inside **Views + flexWrap**? → Refactor to **parent `Text` + nested `Text` / `Animated.Text`**.
2. Are paragraph styles on the parent so inheritance is consistent?
3. No `Fragment` inside `Text`; use array / `flatMap`.
4. Placeholder vs visible text: same dimensions?
