skip to content

With React Native Web, what does Platform report in a browser, and when is a .web.tsx file better than an inline Platform.OS check?

level: middleimportance: should knowfreq 36%

answer

  1. OS is the string 'web'
  2. Version is a placeholder
  3. select ignores the native key on web
  4. imports run before any branch
  5. same exports in both files

basics

~10 s

In React Native Web, Platform.OS is 'web' and Platform.select picks the web key, then default. Prefer a .web.tsx file when the native version imports a native-only package: an inline branch still evaluates that import.

solid answer

~40 s

React Native Web's `Platform` reports `OS: 'web'`, a placeholder `Version` of `'0.0.0'`, and a `select` that returns `web` if present, otherwise `default`; unlike iOS and Android it never falls back to a `native` key, so `Platform.select({ ios: a, android: b })` is `undefined` in a browser. An inline `Platform.OS === 'web'` check is fine for small differences. A `.web.tsx` file next to the base file is better when the native implementation **imports** something that has no web build: an import runs when the module loads, before any branch, so a native-only package that touches its native side at module scope crashes the web bundle even if the branch is never taken. A platform file keeps that import out of the web bundle. Both files must export the same API.

go deeper

for a junior

Recall that Platform.OS is 'web' in the browser and that a .web.tsx file replaces the base file in the web build.

for a middle

Explain Platform.select's web and default lookup, the undefined result when only ios and android keys exist, and why import evaluation makes platform files necessary.

for a senior

Show how you structure platform files so they cannot drift: a shared props type, a base file for TypeScript, and native-only packages imported only from native files.

for a principal

Set the team rule for when divergence moves from inline checks to platform files to a separate web surface, and how that choice is reviewed.

## What Platform says in a browser **React Native Web** ships its own `Platform` module: | Member | Value in React Native Web | |---|---| | `Platform.OS` | `'web'` | | `Platform.Version` | `'0.0.0'`, a placeholder: never use it for browser detection | | `Platform.isTesting` | `true` only when `NODE_ENV` is `'test'` | | `Platform.select(spec)` | `spec.web` if the key exists, otherwise `spec.default` | The `select` rule is the trap. On iOS, React Native's `select` checks `ios`, then `native`, then `default`; Android does the same with `android`. React Native Web checks only `web` and `default`. So: - `Platform.select({ native: 16, default: 12 })` gives `12` on the web, which is usually what you want. - `Platform.select({ ios: 16, android: 14 })` gives **`undefined`** on the web, and a style or prop that relied on it silently disappears. Always give a `default` (or a `web` key) once the app has a web target. ## Inline checks: fine for small differences An inline `Platform.OS === 'web'` check is the right tool for a value, a style or a prop that differs: a larger hit area for touch, a different font, hiding a button that makes no sense in a browser. The code stays in one file and the difference is visible where it applies. ## Why an inline check cannot fix a native-only import Consider a scan screen: ```tsx import { Platform, Text } from 'react-native'; import { NativeScanner } from 'some-native-scanner'; // no web build export function ScanScreen() { if (Platform.OS === 'web') return <Text>Scanning is only on the app.</Text>; return <NativeScanner />; } ``` The branch never renders `NativeScanner` on the web, yet the web bundle still crashes. **Imports are evaluated when the module loads**, before the component function runs. If the package looks up its native module at module scope (for example through `TurboModuleRegistry.getEnforcing`, which React Native Web does not even export, or Expo's `requireNativeModule`, which throws in a browser), the crash happens during import. A platform file fixes it structurally: - `ScanScreen.tsx` holds the native version and imports the native package. - `ScanScreen.web.tsx` holds the web version and never imports it. - Callers import `./ScanScreen`; the bundler picks the `.web.tsx` file for the web build, so the native package is not in the web bundle at all. ## When to prefer a platform file 1. The native version **imports a package with no web implementation**. 2. The two versions **differ in structure**, not in a value: a camera view on the phone, a file upload in the browser. 3. The web version wants **web-only elements**: in a `.web.tsx` file you can render React DOM elements such as `input` directly, because React Native Web renders through React DOM. 4. You want **bundle separation**: web code does not ship in the app and the reverse. ## A decision rule you can say out loud - **A value differs** (a size, a color, a label): inline `Platform.OS` check or `Platform.select` with a `default`. - **A few props or children differ** in an otherwise shared component: inline check, kept small. - **The implementation differs, or imports something native-only**: platform file pair with a shared props type. - **Most of a screen's behaviour differs**: platform file for the whole screen, re-exported from the route. Reviewers can apply the same rule, which keeps a shared codebase from drifting into a mix of styles. ## Rules that keep platform files safe - **Same exports, same props.** Put the shared props type in a third file both implementations import, so they cannot drift. - **Keep a base file.** TypeScript does not know about platform suffixes by default, so it resolves `./ScanScreen` to `ScanScreen.tsx`; without a base file the import has no types. - **`.native.tsx`** covers iOS and Android together when only the web differs. - **In Expo Router**, a platform-specific route file such as `about.web.tsx` is only allowed next to a non-platform `about.tsx`, so every route exists on every platform for deep links; put larger platform-specific screens outside the routes directory and re-export them. Expo also removes code behind `Platform.OS` and `Platform.select` checks from other platforms' production bundles ("platform shaking"), but only when `Platform` is imported directly from `react-native` in that file. That trims dead branches; it is not a substitute for keeping a crashing import out of the web bundle.

  • With React Native Web, what does Platform.select({ ios: 'a', android: 'b' }) return in a browser?
    `undefined`. React Native Web's `select` only looks for a `web` key and then `default`; it never falls back to `native`, `ios` or `android`. Any style or prop computed this way silently vanishes on the web, so specs used in shared code need a `default` or `web` entry.
  • In a project with Button.tsx and Button.web.tsx, how do you stop the two implementations drifting apart?
    Define the props type once in a shared module (for example `Button.types.ts`) and have both files import and use it, and export the same names from both. TypeScript checks callers against the base file, so a web file that quietly changes its props would otherwise only fail at runtime in the browser.
  • Does Expo's platform shaking make an inline Platform.OS check equivalent to a .web.tsx file?
    No. Platform shaking removes the code inside branches that cannot run on the target platform, in production, when `Platform` is imported directly from `react-native`. It does not guarantee the native-only import at the top of the file is dropped or never evaluated, and it does not apply in development. A platform file keeps the import out of the web bundle in every mode.

saying these in an interview costs you the question

  • Platform.select falls back to the native key on the web.
  • Platform.Version on the web reports the browser version.
  • An inline Platform.OS check prevents a native-only import from loading on the web.
  • A .web.tsx file can export a different API from its base file.
  • Platform.OS is 'browser' under React Native Web.