skip to content

In React Native, how do the Text props numberOfLines and ellipsizeMode truncate text, and what limit does Android put on ellipsizeMode?

level: juniorimportance: should knowfreq 46%

answer

  1. truncate after line wrapping
  2. numberOfLines default 0 means unlimited
  3. head, middle, tail, clip
  4. ellipsizeMode defaults to tail
  5. Android multi-line: only tail works

basics

~20 s

numberOfLines caps a React Native Text at N laid-out lines (0, the default, means no limit), and ellipsizeMode picks where the cut shows: head, middle, tail (default) or clip. On Android, beyond one line only tail works correctly.

solid answer

~40 s

`numberOfLines` truncates a `Text` so that, after the text has been laid out and wrapped, it shows at most that many lines; its default `0` means no limit, and setting it back to `0` removes the cap. `ellipsizeMode` only matters when `numberOfLines` is set and chooses where the ellipsis goes: `'tail'` (the default, `abcd…`), `'head'` (`…wxyz`), `'middle'` (`ab…yz`) or `'clip'`, which cuts at the edge without an ellipsis. On Android, when `numberOfLines` is greater than `1`, only `'tail'` works correctly, so `'head'` and `'middle'` are single-line tools in a cross-platform app. Truncation also needs a bounded width: a `Text` inside a `flexDirection: 'row'` container has `flexShrink: 0` by default, so it can overflow instead of truncating until you give it `flexShrink: 1` or `flex: 1`.

code

tsx · 24 lines
tsx
import {Image, StyleSheet, Text, View} from 'react-native';

type Props = {title: string; opening: string; avatarUri: string};

export function PoemRow({title, opening, avatarUri}: Props) {
  return (
    <View>
      <View style={styles.row}>
        <Image source={{uri: avatarUri}} style={styles.avatar} />
        <Text style={styles.title} numberOfLines={1}>
          {title}
        </Text>
      </View>
      <Text numberOfLines={2}>{opening}</Text>
    </View>
  );
}

const styles = StyleSheet.create({
  row: {flexDirection: 'row', alignItems: 'center'},
  avatar: {width: 32, height: 32, marginRight: 8},
  // Without flexShrink the title keeps its natural width and overflows the row.
  title: {flexShrink: 1, fontWeight: 'bold'},
});

go deeper

for a junior

Recall the two props, the default of 0 meaning no limit, the four ellipsizeMode values with tail as default, and that ellipsizeMode needs numberOfLines.

for a middle

Explain that truncation happens after wrapping, the Android rule that only tail works beyond one line, and why a Text in a row needs flexShrink to truncate at all.

for a senior

Show how you design previews that truncate identically on both platforms, keep list rows even, and still give users a path to the full text.

for a principal

Weigh visual consistency from hard line caps against content loss and localisation, where translated strings can double in length and change what gets cut.

## What the two props do `Text` lays out its content first, wrapping words onto as many lines as its width requires. **`numberOfLines`** then cuts that result so the visible text does not exceed the given number of lines, and marks the cut with an ellipsis. - **Default `0`**: no limit. Setting the prop to `0` later is how you remove a cap, for example when a user expands a preview. - **Any positive number**: at most that many lines are shown; the rest is hidden, not scrolled. - The cap applies to the **laid-out lines**, so the same string can need one line on a tablet and three on a phone. **`ellipsizeMode`** only has an effect together with `numberOfLines`. It decides which part of the text is sacrificed: | Value | Shown as | Typical use | |---|---|---| | `'tail'` (default) | `The fog comes on li…` | titles, previews, body text | | `'head'` | `…ttle cat feet` | a path or breadcrumb where the end matters | | `'middle'` | `Chapter…final.txt` | file names or identifiers where both ends matter | | `'clip'` | text cut at the edge, no ellipsis | decorative or fixed-width text | ## The Android limitation On Android, **when `numberOfLines` is greater than `1`, only `'tail'` works correctly**. `'head'` and `'middle'` are therefore reliable only for single-line text across both platforms. A multi-line preview that should truncate at the start or in the middle needs a different design, or platform-specific handling. ## Why truncation sometimes does not happen A cap on lines only helps if the `Text` knows how wide it may be. React Native's flexbox (Yoga) differs from the web in one default that matters here: 1. `flexShrink` defaults to **`0`** in React Native, not `1`. 2. In a `flexDirection: 'row'` container, a long `Text` next to an icon therefore keeps its natural width and **overflows** the row instead of shrinking. 3. Because its width is never constrained, the text does not wrap, so `numberOfLines={1}` has nothing to cut and the title runs off the screen or under the neighbouring icon. 4. Giving the `Text` `flexShrink: 1` (or `flex: 1`) lets it shrink to the space left, and truncation starts working. In a column layout the `Text` is usually stretched to the container's width, so the problem mostly appears in rows. ## A poetry list example A catalogue of poems typically shows each poem's title and its opening lines: - the **title** gets `numberOfLines={1}` with the default `'tail'`, inside a row with an author avatar, and `flexShrink: 1` so it truncates next to the avatar; - the **first lines** get `numberOfLines={2}` so every row has the same height, which keeps the list visually even; - a long **file name** for an attached recording can use `numberOfLines={1}` with `ellipsizeMode="middle"`, since both the start and the extension matter. ## Debugging a cap that looks wrong When truncation does not match expectations, check these in order: 1. **Is `numberOfLines` on the right `Text`?** On a nested `Text` inside a paragraph it does not cap the paragraph; put it on the outer `Text`, which owns the lines. 2. **Is the width bounded?** In a row, look for a missing `flexShrink: 1`; in an absolutely positioned container, look for missing `left`/`right` or `width`. 3. **Is the mode supported?** A three-line `'middle'` preview that looks right on iOS and wrong on Android is the documented Android limitation, not a bug in your layout. 4. **Is the string itself the problem?** Hard newlines in the data count as line breaks during layout, so a stanza with a blank second line spends one of its three lines on nothing. ## Things these props do not do - They do not tell you **whether** the text was truncated; that needs `onTextLayout` and a measurement of the full text. - They do not shrink the font to fit; font scaling to fit is a separate set of props. - They do not add a "more" affordance; any expand control is your own component. - Truncating hides content visually, so the full text should still be reachable, for example on a detail screen.

  • In a React Native row, why does a Text with numberOfLines={1} run past the screen edge instead of showing an ellipsis?
    In React Native `flexShrink` defaults to `0`, so a `Text` inside a `flexDirection: 'row'` container keeps its natural width and overflows rather than shrinking. Its width is never constrained, so there is nothing to truncate. Adding `flexShrink: 1` or `flex: 1` to the `Text` lets it shrink to the remaining space, and `numberOfLines` then cuts it with an ellipsis.
  • In React Native, how do you remove a numberOfLines cap when a user taps to expand a preview?
    Render the same `Text` with `numberOfLines` set to `0`, which the prop treats as no limit, for example `numberOfLines={expanded ? 0 : 3}`. The text is laid out again at full length. Knowing whether to show the expand control at all is a separate problem that needs the text's measured line count.

saying these in an interview costs you the question

  • numberOfLines counts newline characters in the string rather than laid-out lines.
  • ellipsizeMode 'middle' works the same on Android for a three-line preview as on iOS.
  • ellipsizeMode alone truncates text even when numberOfLines is not set.
  • numberOfLines={0} hides the text entirely.
  • A Text in a row truncates automatically because flexShrink defaults to 1, as on the web.