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?
answer
- CALayer shadow under the hood
- shadowOpacity defaults to 0
- offset defaults to 0 and -3
- opaque background: fast shadow path
- boxShadow: New Architecture, both platforms
basics
~20 sReact 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 linesimport { 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
Recall that iOS shadows need shadowColor, shadowOffset, shadowOpacity and shadowRadius, and that shadowOpacity starts at 0.
Explain the defaults, the opacity-times-alpha rule, why an opaque background yields a shadow path, and what boxShadow adds.
Diagnose clipped or expensive shadows in card lists and choose between layer shadows, boxShadow and a wrapper view.
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