In a React Native Pressable, how does the android_ripple prop work, and why might a configured ripple never appear?
answer
- native ripple drawable on Android
- color, borderless, radius, foreground, alpha
- needs color, borderless or radius
- foreground draws above children
- iOS ignores it entirely
basics
~20 sandroid_ripple draws Android's native ripple on a Pressable, configured by color, borderless, radius, foreground and alpha. It is enabled only when color, borderless or radius is set, iOS ignores it, and a covering child hides it unless foreground is true.
solid answer
~50 s`android_ripple` takes a `RippleConfig` — `color`, `borderless`, `radius`, `foreground` and `alpha` — and turns it into a native ripple drawable on the Pressable's view; on press React Native moves the ripple's hotspot to the touch point, so it spreads from the finger. Three things make a ripple silently not appear. First, the hook only enables it when `color`, `borderless` or `radius` is set, so `android_ripple={{}}` or `{ foreground: true }` alone does nothing. Second, by default the ripple is drawn as the view's background, so a child that fills the Pressable with its own background or an image covers it; `foreground: true` draws it on top instead. Third, it is Android-only: iOS ignores it, so iOS needs its own pressed feedback through the `style` function or children function. Since 0.86 `color` may be a `PlatformColor`, which follows light and dark theme changes.
code
tsx · 24 linesimport { Image, Platform, Pressable, StyleSheet } from 'react-native';
type Props = { uri: string; onOpen: () => void };
export function PolicyThumbnail({ uri, onOpen }: Props) {
return (
<Pressable
onPress={onOpen}
android_ripple={{ color: 'rgba(0, 0, 0, 0.12)', foreground: true }}
style={({ pressed }) => [
styles.frame,
Platform.OS === 'ios' && pressed ? styles.pressed : null,
]}
>
<Image source={{ uri }} style={styles.image} />
</Pressable>
);
}
const styles = StyleSheet.create({
frame: { borderRadius: 8, overflow: 'hidden' },
pressed: { opacity: 0.7 },
image: { width: 96, height: 96 },
});go deeper
Recall that android_ripple on Pressable gives the Android ripple, that iOS ignores it, and that color is the field to set first.
Explain the config fields, the enable condition on color, borderless or radius, and background versus foreground drawing when children have backgrounds.
Build press feedback that behaves on both platforms, and diagnose invisible or overflowing ripples in dense lists and icon rows.
Own the press-feedback rule in the design system: native ripple on Android, a pressed style on iOS, shared tokens so both stay consistent.
## What `android_ripple` is Material Design's touch feedback on Android is the **ripple**: a circle of colour that spreads from the touch point. React Native's `Pressable` exposes it through the Android-only **`android_ripple`** prop. It is not an animation written in JavaScript; React Native hands Android a native ripple drawable, and Android animates it on the UI side. The prop takes a **`RippleConfig`** object: | Field | Meaning | | --- | --- | | `color` | Ripple colour; a colour value or, since 0.86, a `PlatformColor` | | `borderless` | `true` lets the ripple extend beyond the view's bounds | | `radius` | Ripple radius | | `foreground` | `true` draws the ripple over the children instead of as the background | | `alpha` | Ripple opacity from `0.0` to `1.0`, applied on top of the colour's own alpha | ## How it works under the hood Pressable calls an internal hook that, **only on Android and only when `color`, `borderless` or `radius` is not null**, builds a native ripple description and passes it to the view as either its native background or its native foreground. During the press it: 1. moves the ripple's **hotspot** to the touch coordinates on press-in and while the finger moves, so the ripple spreads from the finger; 2. sets the native view's **pressed** state on press-in, which starts the ripple; 3. clears the pressed state on press-out. The ripple itself is drawn and animated natively, not frame by frame in JavaScript. Its **start**, however, comes from Pressable's press-in handling in JavaScript, so a JavaScript thread that is already blocked when the finger lands delays the ripple along with every other press response. ## Why a configured ripple never appears - **Nothing that enables it is set.** `android_ripple={{}}` or `android_ripple={{ foreground: true }}` passes a config, but without `color`, `borderless` or `radius` the hook returns nothing and no ripple is attached. `{ color: '#00000022' }` is the minimal working config. - **A child covers it.** By default the ripple is the Pressable's **background**. If the only child is an `Image` or a `View` with its own `backgroundColor` that fills the Pressable, the ripple is painted underneath it. `foreground: true` moves the ripple above the children. - **It is iOS.** On iOS the prop is ignored. Pressed feedback there comes from Pressable's `style` function (`({ pressed }) => ...`) or its children function. - **Borderless surprises.** A `borderless` ripple is not clipped to the view, so it can spread over neighbours, which looks like a bug on a small icon button inside a row. ## Ripple versus a pressed style | | `android_ripple` | `style={({ pressed }) => ...}` | | --- | --- | --- | | Platforms | Android only | iOS and Android | | Drawn by | Android's native ripple drawable | React Native re-rendering the style | | Spreads from the touch point | Yes, via the hotspot | No, the whole view changes | You can combine both on Android — a ripple plus a subtle pressed style — but using only the ripple on Android and only the style on iOS keeps each platform idiomatic. ## Cross-platform press feedback A robust pattern combines both mechanisms: ```tsx <Pressable android_ripple={{ color: 'rgba(0, 0, 0, 0.12)', foreground: true }} style={({ pressed }) => [ styles.row, Platform.OS === 'ios' && pressed ? styles.rowPressed : null, ]} onPress={openPolicy} > <Image source={thumbnail} style={styles.thumb} /> </Pressable> ``` On Android the foreground ripple sits above the image; on iOS the pressed style dims the row. ## Related Android-only props - **`android_disableSound`** — `false` by default; set `true` to suppress the system click sound on press. - **`TouchableNativeFeedback`** — the older Android-only wrapper that also draws native feedback. React Native's docs point to `Pressable` as the more extensive and future-proof API, and new code should use `android_ripple` on `Pressable`. ## Interview summary Describe the native ripple and its config fields, then the three silent failures: the enabling fields, background versus foreground under a covering child, and iOS ignoring it. Mention that the ripple animates natively but is started from Pressable's JavaScript press-in handling.
- In a React Native Pressable, why does android_ripple={{ foreground: true }} show no ripple at all?Pressable only builds the native ripple when `color`, `borderless` or `radius` is set. `foreground` alone just chooses where the ripple would be drawn, so nothing is attached. Adding a `color` is enough to enable it.
- What changed for android_ripple colours in React Native 0.86?The `color` field accepts a `PlatformColor`, so a ripple can reference an Android theme attribute such as `?attr/colorAccent`. The ripple then updates automatically when the system configuration changes, for example when the user switches between light and dark mode.
saying these in an interview costs you the question
- android_ripple also animates the press on iOS
- An empty android_ripple object enables the default ripple
- The ripple is animated frame by frame in JavaScript
- foreground: true is needed for every ripple to appear
- TouchableNativeFeedback is the recommended way to get a ripple in new code