skip to content

iOS-Only APIs

Some APIs exist only on iOS: ActionSheetIOS, DynamicColorIOS, Settings over user defaults, Alert.prompt and the shadow style props. Interviewers ask how each maps to an Android equivalent.

part ofReact Nativeoverview, primer and where to startread it →
on this pageshow

explore

questions

6

In React Native, why does a view with only shadowColor set show no shadow on iOS, and when would you use boxShadow instead of the shadow props?

level: middleimportance: must knowfreq 55%

answer

  1. CALayer shadow under the hood
  2. shadowOpacity defaults to 0
  3. offset defaults to 0 and -3
  4. opaque background: fast shadow path
  5. boxShadow: New Architecture, both platforms

basics

~20 s

React Native's shadow props map to the iOS layer shadow, and shadowOpacity defaults to 0, so shadowColor alone draws nothing. boxShadow is the web-style alternative that also renders on Android, supports spread, inset and multiple shadows, and needs the New Architecture.

solid answer

~50 s

`shadowColor`, `shadowOffset`, `shadowOpacity` and `shadowRadius` set the view layer's shadow on iOS. React Native only draws it when `shadowOpacity` is above 0 and the colour is not transparent, and `shadowOpacity` defaults to 0 — so `shadowColor` alone shows nothing; the defaults for offset and radius are `{ width: 0, height: -3 }` and 3. With an opaque `backgroundColor`, React Native gives the layer a shadow path from the border box; without one, iOS derives the shadow from the rendered pixels, so text and icons cast it and it costs more to draw. On Android only `shadowColor` applies, tinting `elevation`. `boxShadow` takes a CSS-like string or `BoxShadowValue` objects, supports blur, spread, `inset` and several shadows, renders on iOS and Android, and requires the New Architecture — the only one since 0.82. Choose it when the design specifies web-style shadows or needs one value for both platforms.

code

tsx · 19 lines
tsx
import { StyleSheet } from 'react-native';

export const styles = StyleSheet.create({
  // iOS layer shadow: all four props, opaque background for a cheap shadow path
  postCard: {
    backgroundColor: '#ffffff',
    borderRadius: 12,
    shadowColor: '#000000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.15,
    shadowRadius: 6,
  },
  // One value for iOS and Android on the New Architecture
  postCardBoxShadow: {
    backgroundColor: '#ffffff',
    borderRadius: 12,
    boxShadow: '0 2px 6px rgba(0, 0, 0, 0.15)',
  },
});

go deeper

for a junior

Recall that iOS shadows need shadowColor, shadowOffset, shadowOpacity and shadowRadius, and that shadowOpacity starts at 0.

for a middle

Explain the defaults, the opacity-times-alpha rule, why an opaque background yields a shadow path, and what boxShadow adds.

for a senior

Diagnose clipped or expensive shadows in card lists and choose between layer shadows, boxShadow and a wrapper view.

for a principal

Set a shadow token strategy for the design system: platform-native depth or one boxShadow scale shared by both platforms.

## Two models React Native offers two ways to draw a box shadow: | | `shadow*` props | `boxShadow` | | --- | --- | --- | | Platforms | iOS (Android: `shadowColor` tints `elevation` only) | iOS and Android | | Maps to | The view layer's native shadow | React Native's own shadow layers, spec-style | | Values | `shadowColor`, `shadowOffset`, `shadowOpacity`, `shadowRadius` | String like `'0 2px 8px rgba(0,0,0,0.2)'` or array of `BoxShadowValue` | | Spread, inset, multiple | No | Yes (inset needs Android 10+, outset Android 9+) | | Architecture | Any | New Architecture only | The Android elevation side is its own subject; here the question is iOS behaviour and when to switch models. ## Why `shadowColor` alone shows nothing On iOS, React Native copies each `shadow*` prop onto the view's layer. Before drawing, it checks that **`shadowOpacity` is greater than 0 and the colour's alpha is greater than 0**. The defaults in React Native's view props are: - `shadowOpacity`: **0** - `shadowOffset`: **`{ width: 0, height: -3 }`** (a small upward offset) - `shadowRadius`: **3** - `shadowColor`: unset So a style with only `shadowColor: '#000'` has zero opacity and draws nothing. Setting `shadowOpacity: 0.2` makes the default offset and radius visible — often an upward shadow nobody asked for, which is why shadows usually set all four props. Note that `shadowOpacity` is **multiplied by the colour's alpha**: `shadowColor: 'rgba(0,0,0,0.5)'` with `shadowOpacity: 0.5` gives an effective 25%. ## The background matters When a view has a shadow, React Native decides how the layer computes it: 1. **Opaque `backgroundColor`** — it builds a **shadow path** from the view's rounded border box. The shadow follows the card's shape exactly and is cheap for iOS to render. 2. **Transparent or semi-transparent background** — it leaves the path empty, and iOS falls back to a **pixel-based** shadow computed from whatever the view draws. Children's text and icons each cast a shadow, and rendering costs more, which can show up as dropped frames in long lists of cards. So a card with shadow props should have an opaque background on the view that carries the shadow. ## `overflow: 'hidden'` and rounded images A classic iOS bug is a card with `borderRadius`, `overflow: 'hidden'` (to clip an image) and a shadow: clipping the view's content to its bounds clips the layer shadow too. The usual fix is two views — an outer one with the shadow and background, an inner one with `overflow: 'hidden'` and the radius. `boxShadow` is implemented so that its shadow is drawn outside the clipping container, which is one practical reason to prefer it on the New Architecture. ## Mistakes that look like platform bugs - **Only `shadowColor` set**: opacity is still 0, so nothing is drawn. - **Only `shadowOpacity` set**: the default upward offset of `-3` and radius of `3` appear, so the shadow sits on top of the card instead of below it. On iOS a positive `height` moves the shadow down. - **Semi-transparent colour and opacity**: the two multiply, and the shadow looks far weaker than the design. - **Shadow on a transparent wrapper** around the real card: pixel-based fallback, shadows under every child. - **`overflow: 'hidden'` on the shadowed view**: the clipping removes the shadow. - **Expecting the same result on Android**: only `shadowColor` crosses over, as a tint for `elevation`. ## When to choose `boxShadow` - **The design comes from web tooling** with offset, blur, spread and colour — `boxShadow` takes exactly those values. - **You want one value for both platforms** instead of `shadow*` for iOS plus `elevation` for Android. - **You need spread, inset or layered shadows**, which the layer shadow cannot express. - **Clipping and shadow on one view**, as above. Stay with `shadow*` when a simple native shadow on iOS is enough; React Native's docs recommend the platform props for straightforward shadows because they map directly to platform APIs. ## Interview summary Explain the opacity default, the other defaults, the background-driven shadow path, the clipping trap, and the `boxShadow` trade-off. That covers what interviewers mean by "shadows are different on iOS".

  • In React Native on iOS, why do the text labels inside a card with shadow props suddenly get their own shadows?
    The card's background is transparent, so React Native cannot build a shadow path from the border box and iOS falls back to a pixel-based shadow computed from everything the view draws. Give the shadowed view an opaque `backgroundColor` and the shadow follows the card's shape instead.
  • What does shadowOpacity multiply in React Native's iOS shadow props?
    It is multiplied by the alpha of `shadowColor`. A 50% transparent colour with `shadowOpacity: 0.5` produces a shadow at an effective 25% opacity, which is why teams usually keep the colour opaque and control strength with `shadowOpacity`.

saying these in an interview costs you the question

  • shadowColor alone is enough to draw an iOS shadow
  • shadowOffset defaults to zero
  • The iOS shadow props also work on Android
  • boxShadow works on the legacy architecture
  • A transparent background makes the shadow cheaper to render
open as a page

In React Native, how do you show a native iOS action sheet with ActionSheetIOS.showActionSheetWithOptions, and what happens if the same call runs on Android?

level: juniorimportance: should knowfreq 35%

basics

~20 s

ActionSheetIOS.showActionSheetWithOptions takes an options array of button titles plus indices for cancel and destructive buttons, and calls back with the zero-based index tapped. It is iOS-only: on Android the native module is missing and the call throws.

open as a page

In React Native, what does Alert.prompt do on iOS, and why does the same text-input prompt never appear on Android?

level: middleimportance: should knowfreq 25%

basics

~20 s

Alert.prompt shows an iOS alert with a text field and passes the text to a callback or to the pressed button's onPress. Its body only runs when Platform.OS is ios, so on Android it returns silently: no dialog, no warning.

open as a page

In React Native, how does DynamicColorIOS pick a colour for light and dark mode, and why can Platform.select({ ios: DynamicColorIOS(...) }) crash on Android?

level: middleimportance: should knowfreq 22%

basics

~20 s

DynamicColorIOS({ 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.

open as a page

A React Native forum app's share-and-report sheet works on iOS, but on Android it crashes or silently does nothing; how do the iOS-only APIs fail there, and how do you guard them?

level: seniorimportance: should knowfreq 28%

basics

~20 s

React Native's iOS-only APIs fail on Android in three ways: ActionSheetIOS and DynamicColorIOS throw, Alert.prompt returns silently, and Settings warns and returns null. Guard each call site with Platform.OS or hide them behind one cross-platform wrapper per capability.

open as a page

In React Native, what does the iOS-only Settings module store, and why does Settings.watchKeys not fire after your own Settings.set call?

level: middleimportance: nice to knowfreq 12%

basics

~20 s

Settings wraps iOS NSUserDefaults: get reads synchronously from a JavaScript copy, set writes through to native. watchKeys only reports changes made outside React Native code, such as the Settings app, because the native side ignores notifications caused by set.

open as a page