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?
answer
- OS is the string 'web'
- Version is a placeholder
- select ignores the native key on web
- imports run before any branch
- same exports in both files
basics
~10 sIn 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 sReact 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
Recall that Platform.OS is 'web' in the browser and that a .web.tsx file replaces the base file in the web build.
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.
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.
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.