skip to content

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