In React Native, how does DynamicColorIOS pick a colour for light and dark mode, and why can Platform.select({ ios: DynamicColorIOS(...) }) crash on Android?
answer
- one colour value, resolved natively
- light and dark required
- high-contrast keys fall back
- throws off iOS
- object literal evaluates every branch
basics
~20 sDynamicColorIOS({ light, dark }) returns one colour that iOS resolves natively for the current appearance, with optional high-contrast variants. On other platforms the function throws, and Platform.select's object literal evaluates every branch first, so the iOS branch still runs on Android.
solid answer
~40 s`DynamicColorIOS` takes an object with required `light` and `dark` colours and optional `highContrastLight` and `highContrastDark`, which fall back to `light` and `dark`. It returns a single colour value; iOS resolves it at draw time from the current appearance and accessibility contrast, so the colour flips when the user switches to dark mode without React re-rendering. It is iOS-only in a strict way: the non-iOS implementation throws `DynamicColorIOS is not available on this platform.` The trap is `Platform.select({ ios: DynamicColorIOS({...}), android: '#222' })`: JavaScript evaluates every value of the object literal before `Platform.select` chooses one, so the iOS call runs on Android and throws — at module load if it sits in `StyleSheet.create`. Guard the call itself, for example `Platform.OS === 'ios' ? DynamicColorIOS({...}) : '#222'`.
code
tsx · 10 linesimport { DynamicColorIOS, Platform, StyleSheet } from 'react-native';
const titleColor =
Platform.OS === 'ios'
? DynamicColorIOS({ light: '#111111', dark: '#f2f2f2' })
: '#222222';
export const styles = StyleSheet.create({
postTitle: { color: titleColor, fontSize: 17 },
});go deeper
Recall that DynamicColorIOS takes light and dark colours, is iOS-only, and must never be called on Android.
Explain native resolution without re-renders, the high-contrast fallbacks, and why Platform.select's object literal still runs the iOS call on Android.
Decide between native dynamic colours and a JavaScript theme, and structure style modules so iOS-only calls cannot load on Android.
Own the theming architecture: which colours follow the system natively, which follow in-app theme state, and how both stay consistent.
## What DynamicColorIOS is iOS can describe a colour not as one RGB value but as a **dynamic colour**: a set of values the system chooses between at draw time according to the current **appearance** (light or dark) and **accessibility contrast** setting. React Native exposes this through **`DynamicColorIOS`**, a function exported from `react-native`. Its documentation compares it to UIKit's dynamic-provider colours. ```tsx const postTitle = DynamicColorIOS({ light: '#111111', dark: '#f2f2f2', highContrastLight: '#000000', highContrastDark: '#ffffff', }); ``` | Key | Required | Used when | | --- | --- | --- | | `light` | yes | Light appearance | | `dark` | yes | Dark appearance | | `highContrastLight` | no, falls back to `light` | Light appearance with increased contrast | | `highContrastDark` | no, falls back to `dark` | Dark appearance with increased contrast | ## How it resolves `DynamicColorIOS` returns an opaque **colour value** you can use anywhere a colour is accepted — `color`, `backgroundColor`, `borderColor`, `tintColor`. It is not a string and not a hook. The value is handed to the native side, and **iOS picks the variant when it draws**. Consequences: - Switching the system to dark mode updates the colour **without a React re-render**. - The choice follows the **system** appearance of that view; it is not driven by any JavaScript theme state. - It is useful for **brand colours** that must still respond to appearance and contrast, which system-named platform colours do not cover. A JavaScript theme built on the colour-scheme hook works differently: React re-renders with a new palette when the scheme changes. Both are valid; `DynamicColorIOS` is cheaper to flip but only exists on iOS. ## Why it crashes on Android React Native ships two implementations. The iOS one builds the dynamic colour value. The implementation used on every **other** platform is a single line: ```tsx throw new Error('DynamicColorIOS is not available on this platform.'); ``` So any path that **calls** `DynamicColorIOS` on Android throws. The surprising path is `Platform.select`: ```tsx const styles = StyleSheet.create({ title: { color: Platform.select({ ios: DynamicColorIOS({ light: '#111', dark: '#f2f2f2' }), // runs on Android too android: '#222', }), }, }); ``` `Platform.select` is an ordinary function that receives an **already-built object**. JavaScript evaluates every property value of the object literal — including the `ios` one — before the function runs and picks a key. On Android the `DynamicColorIOS` call therefore executes and throws. Inside `StyleSheet.create` at the top of a module, that happens **when the module is first imported**, so the Android app fails as soon as that screen's file loads. ## Safe patterns 1. **Guard the call, not the result**: `Platform.OS === 'ios' ? DynamicColorIOS({...}) : '#222'`. 2. **Keep it in an iOS-only module** so Android never imports the call site. 3. **Pass functions to `Platform.select`** and call the chosen one: `Platform.select({ ios: () => DynamicColorIOS({...}), default: () => '#222' })()`. ## DynamicColorIOS versus a JavaScript theme | | `DynamicColorIOS` | Theme from the colour-scheme hook | | --- | --- | --- | | Resolved by | iOS, at draw time | React, at render time | | Re-render on appearance change | Not needed | Needed | | Platforms | iOS only | iOS and Android | | In-app override of the system setting | No | Yes, if the app keeps its own theme state | | High-contrast variants | Built in | Must be modelled by the app | To check a screen, switch the device between light and dark appearance and turn on the increased-contrast accessibility setting: each colour built with `DynamicColorIOS` should change without the screen re-mounting. ## Where it fits - **Only the colour is dynamic.** Images, shadows and layout do not switch with it. - **The system decides the appearance.** An in-app "dark mode" toggle that differs from the system setting needs JavaScript theme state instead. - **Android has its own theme-aware colours** through platform colour references, a separate subject. ## Interview summary Describe the two required keys and the high-contrast fallbacks, that iOS resolves the colour natively without a re-render, and the eager-evaluation trap with `Platform.select`. The trap is what separates someone who has shipped it from someone who has read about it.
- In React Native, what happens to a Text using a DynamicColorIOS colour when the user switches iOS to dark mode?The text switches to the `dark` value without React re-rendering, because the colour value is resolved natively at draw time from the current appearance. A palette read from the colour-scheme hook, by contrast, changes only when React re-renders.
- In React Native, what does DynamicColorIOS use if highContrastDark is omitted and the user enables increased contrast in dark mode?It falls back to the `dark` value. Both high-contrast keys are optional and default to their normal-contrast counterparts, so only colours that need a stronger variant should set them.
Platform.select with a DynamicColorIOS call is like asking a waiter to cook every dish on the menu before you pick one: the kitchen that cannot make the iOS dish fails before your choice is even read.
saying these in an interview costs you the question
- Platform.select only evaluates the branch for the current platform
- DynamicColorIOS returns a hex string
- DynamicColorIOS needs a re-render to switch appearance
- On Android DynamicColorIOS falls back to the light value
- highContrastLight and highContrastDark are required