skip to content

In React Native, what does Appearance.setColorScheme override, and how does a banking app return to following the system on 0.87 versus 0.86?

level: middleimportance: should knowfreq 36%

answer

  1. app-level, not system-wide
  2. native alerts and pickers follow it
  3. 'auto' removes the override in 0.87
  4. 'unspecified' before, null before 0.82
  5. not persisted for you

basics

~20 s

Appearance.setColorScheme('light' | 'dark') forces the app, including its native alerts and pickers, into one scheme without touching the system setting. On 0.87 'auto' removes the override; on 0.86 (Expo SDK 57) the reset value is 'unspecified'.

solid answer

~40 s

`Appearance.setColorScheme()` is an **app-level override**: `'light'` or `'dark'` makes the app, and the native elements it shows such as alerts and pickers, adopt that style, while the system setting and other apps are unaffected. Natively it sets the window's interface style override on iOS and the default night mode on Android. `useColorScheme()` and `getColorScheme()` then report the forced value, and listeners fire. To go back to following the system, React Native 0.87 uses `setColorScheme('auto')`; `'unspecified'` still works but is deprecated. On 0.82 to 0.86, which includes Expo SDK 57 apps on 0.86, `'unspecified'` is the reset value; before 0.82, `null` was. React Native does not remember the choice across launches, so a banking app with a System / Light / Dark setting stores the preference itself and re-applies it at startup.

code

typescript · 8 lines
typescript
import { Appearance } from 'react-native';

export type AppearancePreference = 'system' | 'light' | 'dark';

// React Native 0.87: 'auto' follows the system (0.82-0.86 use 'unspecified').
export function applyAppearance(preference: AppearancePreference): void {
  Appearance.setColorScheme(preference === 'system' ? 'auto' : preference);
}

go deeper

for a junior

Recall that setColorScheme forces light or dark for this app only, and that a reset value makes it follow the system again.

for a middle

Explain what the override affects, including native elements, how the reset value changed across versions, and what the hook reports afterwards.

for a senior

Build a persisted System / Light / Dark setting applied before first render, with one source of truth, and plan the 'unspecified' to 'auto' migration.

for a principal

Decide whether an in-app appearance setting is worth offering beyond following the system, weighing support cost against users' expectations for a banking app.

## What the override does A banking app that follows the phone needs no override at all: `useColorScheme()` reports the system scheme. The override exists for apps that offer an in-app **Appearance** setting with three choices: System, Light and Dark. `Appearance.setColorScheme(value)` changes the scheme **for this app only**: - `'light'` or `'dark'` forces that style. - The override applies to the app and **native elements within it**, such as alerts and pickers, so the whole app looks consistent. - The **system setting** and **other apps** are not affected. - `useColorScheme()`, `Appearance.getColorScheme()` and change listeners all report the **effective** scheme, so existing theming code follows the override automatically. Under the hood, the iOS module sets the interface style override on the app's windows, and the Android module sets the default night mode for the app. That is why native UI follows as well as JavaScript-drawn UI. ## Returning to the system setting The value that removes the override has changed across releases, and this matters because Expo SDK 57 apps run React Native 0.86, not 0.87. | React Native version | Reset value | Notes | |---|---|---| | Before 0.82 | `null` | Nullable argument | | 0.82 to 0.86 | `'unspecified'` | The type no longer accepts `null` | | 0.87 | `'auto'` | `'unspecified'` still accepted but deprecated | So in an Expo SDK 57 project, `setColorScheme('unspecified')` is the reset, and upgrading to a release based on 0.87 means switching to `'auto'` before the deprecated value is removed. ## Building a System / Light / Dark setting 1. Keep the user's choice as a small enum: `'system' | 'light' | 'dark'`. 2. Map it to the override: `'system'` becomes the reset value, the others pass through. 3. **Persist** the choice with the app's storage. React Native keeps the override only while the app runs; it does not save it. 4. On startup, read the stored choice and call `setColorScheme` **before** the first screen renders, or the app flashes the system scheme first. 5. Keep the theming code unchanged: it already reads `useColorScheme()`, which now reports the effective scheme. ## How it interacts with scheduled switches When the override is `'light'` or `'dark'`, a system switch at sunset does not change what the app reports; that is the point of the override. As soon as the app resets to follow the system, the current system value applies and later changes flow through again. The docs note this directly: the scheme may change at runtime when it is **not** overridden via `setColorScheme()`. ## Mistakes to avoid - **Calling `setColorScheme` from render**: it is a side effect that changes native state and emits change events; call it from the settings handler and at startup. - **Maintaining a separate theme flag** alongside the override: the app then has two sources of truth that can disagree, for example native alerts in dark while the JavaScript palette is light. - **Passing `null`** on current versions: the type no longer accepts it. - **Forgetting the upgrade**: code that passes `'unspecified'` keeps working on 0.87 but is deprecated. - **Assuming persistence**: after a cold start without re-applying, the app follows the system again. ## Testing the setting - Choose Dark in the app while the phone is light, and open a screen that shows a native alert: both the JavaScript-drawn UI and the alert should be dark. - Choose System, then flip the phone's setting: the app should follow. - Kill and relaunch with each stored choice, and confirm the first frame already uses it. - On an Expo SDK 57 build, confirm the reset path uses `'unspecified'`, and plan the switch to `'auto'` with the upgrade. ## Why use the override rather than a JavaScript-only flag A JavaScript-only toggle can swap the palette, but native UI outside React's control, alerts, pickers and other system-drawn elements, would still follow the system scheme. The override keeps everything aligned from one call.

  • After setColorScheme('dark'), what does useColorScheme() return while the phone is in light mode?
    `'dark'`. The hook reports the effective scheme for the app, and the override takes precedence over the system preference until it is reset with `'auto'` (on 0.87). That is why theming code built on `useColorScheme()` needs no changes to support an in-app setting.
  • Why must the stored preference be applied before the first screen renders?
    React Native does not persist the override, so at a cold start the app follows the system. If the stored choice is applied after the first render, the screen draws once in the system scheme and then flips, which the user sees as a flash of the wrong colours.

saying these in an interview costs you the question

  • Appearance.setColorScheme changes the phone's system-wide dark mode.
  • setColorScheme(null) is the right reset on React Native 0.87.
  • React Native remembers the override across app launches.
  • After an override, useColorScheme still reports the system value.
  • A JavaScript palette flag alone also switches native alerts and pickers.