skip to content

In React Native Web, which DOM elements do View and Text render, and why does a nested Text become a span?

level: juniorimportance: should knowfreq 42%

answer

  1. a flexbox reset on every host element
  2. column direction, no shrink, relative position
  3. Text: block-level outside, inline inside
  4. a context flag says 'already inside Text'
  5. string directly in View: dev-only error

basics

~20 s

React Native Web renders View as a div carrying a flexbox reset (column direction, no shrink) and Text as a div at the top level but a span inside another Text, so nested runs stay inline and inherit font and color.

solid answer

~30 s

In `react-native-web` a `View` becomes a `div` with a reset class that sets `display: flex`, `flexDirection: 'column'`, `flexShrink: 0`, `position: 'relative'` and zero margins, so Yoga-style layout survives the browser's own defaults. A top-level `Text` also renders a `div`, with its own reset (`font: 14px System`, `whiteSpace: 'pre-wrap'`, `dir="auto"`). The outer `Text` puts a context provider around its children; any `Text` that finds that context renders a `span` whose reset inherits `color`, `font`, `textAlign` and `whiteSpace`, which reproduces React Native's rule that text styles inherit only inside a `Text` subtree. `testID` becomes `data-testid`, `nativeID` becomes `id`, and `className` is simply dropped.

code

tsx · 11 lines
tsx
import { Text, View } from 'react-native';

export function Greeting({ name }: { name: string }) {
  return (
    <View style={{ padding: 16 }}>
      <Text>
        Hello, <Text style={{ fontWeight: 'bold' }}>{name}</Text>
      </Text>
    </View>
  );
}

go deeper

for a junior

Recall the basic mapping: View becomes a div, Text becomes a div at the top level and a span when nested. Mention that styles still go through the style prop.

for a middle

Explain why the flexbox reset exists (Yoga defaults versus CSS defaults) and how a nested Text detects its parent through context and inherits only inside the Text subtree.

for a senior

Show the porting traps: strings in a View that only warn on web but fail on a phone, dropped className, global CSS on div elements, and testID becoming data-testid for web tests.

for a principal

Frame the trade-off: React Native Web keeps component isolation by refusing cascade and className, which protects a shared codebase but makes it harder to mix in an existing CSS design system.

## What React Native Web is doing **React Native Web** (`react-native-web`, version 0.21 at the time of writing) implements React Native's components and APIs on top of React DOM. When a web bundler aliases `react-native` to it, the same `View`, `Text` and `Pressable` code produces HTML instead of native views. The mapping is not a per-component guess: every host component goes through one internal `createElement` helper that picks a tag, turns props into DOM attributes and compiles styles into class names. Knowing what that helper emits is what lets you debug a React Native screen in the browser's element inspector. ## View: a div with a flexbox reset - A `View` renders a **`div`** by default. - It carries a reset class (named like `css-view-…` in development) that sets `display: flex`, `flexDirection: 'column'`, `flexShrink: 0`, `flexBasis: 'auto'`, `alignItems: 'stretch'`, `alignContent: 'flex-start'`, `position: 'relative'`, `zIndex: 0`, `minWidth: 0`, `minHeight: 0`, `boxSizing: 'border-box'` and zero margin, padding and border. - The reset exists because the browser's defaults differ from Yoga's. A plain `div` is block layout; once it is a flex container, CSS defaults to `flex-direction: row` and `flex-shrink: 1`. React Native code assumes column direction and no shrinking, so the reset puts those defaults back. - A `View` changes tag in two cases: an `href` prop turns it into an **`a`**, and some `role` values select a semantic element (for example `role="button"` gives a `button`, `role="navigation"` a `nav`). ## Text: a div outside, a span inside 1. A **top-level `Text`** renders a `div` with its own reset: `font: 14px System`, `color: black`, `whiteSpace: 'pre-wrap'`, `wordWrap: 'break-word'`, and a `dir="auto"` attribute so the browser infers the writing direction of the string. 2. A **`Text` nested inside another `Text`** renders a **`span`**. Its reset inherits `color`, `font`, `textAlign` and `whiteSpace` from the parent instead of setting them. 3. A `View` placed inside a `Text` switches to `display: inline-flex`, so it sits inside the line of text. How does a `Text` know it is nested? The outer `Text` wraps its children in an internal React context provider whose value is `true`; every inner `Text` (and `View`) reads that context. This is the web version of React Native's own rule that **text style inheritance only happens inside a `Text` subtree**. Instead of letting CSS cascade from `body`, React Native Web resets every top-level `Text` and only lets inheritance flow between nested `Text` elements, so a component looks the same wherever it is dropped. ## Props that reach the DOM, and props that do not | React Native prop | What the browser receives | |---|---| | `testID="save"` | `data-testid="save"` | | `nativeID` or `id` | an `id` attribute | | `dataSet={{ userId: 7 }}` | `data-user-id="7"` | | `style` | a `class` of compiled rules, an inline `style`, or both | | `role`, `aria-*` | ARIA attributes (plus a semantic tag for some roles) | | `className` | nothing: it is not on the forwarded-prop list and is dropped | Each host component forwards only an explicit allow-list of props (accessibility, click, focus, keyboard, mouse, pointer, touch, `style`, a few more). Anything else, including `className`, never reaches the element. ## Porting traps worth naming in an interview - **A raw string inside a `View`.** React Native's renderer rejects it with "Text strings must be rendered within a <Text> component." React Native Web only calls `console.error` in development ("Unexpected text node … A text node cannot be a child of a <View>") and the string still appears on the page. A web-first team can ship code that fails on a phone. - **Global text styles do not apply.** Setting `font-family` on `body` in a stylesheet does not reach a top-level `Text`, because its reset sets `font` explicitly. The React Native pattern of an `AppText` wrapper is the fix on web too. - **External CSS that targets every `div`** can interfere with the reset, because every `View` is a `div`. ```tsx import { Text, View } from 'react-native'; export function Price() { return ( <View testID="price-row"> <Text> Total: <Text style={{ fontWeight: 'bold' }}>$42</Text> </Text> </View> ); } // Browser (roughly): div[data-testid=price-row] > div[dir=auto] > "Total: " + span ``` The outer `Text` is a `div` with `dir="auto"`; the bold `Text` is a `span` that inherits the font and color and adds only its own weight.

  • With React Native Web, why does setting font-family on body in a global stylesheet not change the font of a top-level Text?
    Because a top-level `Text` renders with a reset that sets `font: 14px System` and `color: black` explicitly, so nothing cascades in from `body`. Only a `Text` nested in another `Text` inherits. The fix is the same as on native: a shared `AppText` component (or a style constant) that applies the app font, and nested `Text` inside it inherits from there.
  • In React Native Web, what happens if a component passes className to a View?
    Nothing reaches the DOM. `View` forwards only an allow-list of props (accessibility, event handlers, `style`, `id`, `testID`, `dataSet` and a few others), and `className` is not on it, so it is dropped silently. Styling goes through `style`, which React Native Web compiles to its own class names.
  • Why does React Native Web put a flexDirection: 'column' and flexShrink: 0 reset on every View?
    Because React Native's layout engine, Yoga, defaults to column direction and no shrinking, while a CSS flex container defaults to `row` and `flex-shrink: 1`. Without the reset, the same layout code would lay children out horizontally and squash them on the web. The reset makes the browser follow React Native's defaults.

saying these in an interview costs you the question

  • A View renders a native UIView-like element in the browser, not a div.
  • Every Text renders a span, whether or not it is nested.
  • A string placed directly in a View fails on web just as it does on iOS and Android.
  • Passing className to a View is the way to reuse existing CSS classes.
  • The browser's default flex-direction row applies to a View on web.