In React Native 0.87, which style properties can Animated's native driver animate, and what happens if you native-drive a heart's width?
answer
- non-layout properties only
- transform, opacity, colours, radii
- an error for unsupported styles
- one driver per value
- layout props only on the experimental backend
basics
~20 sBy default the native driver animates only non-layout styles: transform, opacity, colours, border radii, shadow opacity and radius, elevation, zIndex and filter. Native-driving width logs an error that the style is not supported; animate transform: scale instead.
solid answer
~50 sThe native driver can only animate properties that do not need a new layout pass. In React Native 0.87 the allowlist covers `transform` (translate, scale, rotate, skew, perspective), `opacity`, colours such as `backgroundColor`, `color`, `borderColor` and `tintColor`, the `borderRadius` family, `shadowOpacity`, `shadowRadius`, `elevation`, `zIndex` and `filter`. Layout properties such as `width`, `height`, margins, padding, `top`/`left` and `flex` are not on it; binding one to a native-driven value makes Animated log an error that the style property is not supported by the native animated module. For a heart pop, animate `transform: [{scale}]`, which looks the same without a layout pass. Two related rules: a value is bound to one driver, so a JS-driven animation on a value that has already run natively throws, and `Animated.event` works natively only with direct events such as `onScroll`. The experimental Shared Animation Backend (0.85.1+) adds layout props, but not in a stable app.
code
tsx · 28 linesimport {Animated, StyleSheet, useAnimatedValue} from 'react-native';
export function HeartBadge() {
const pop = useAnimatedValue(0);
const play = () => {
pop.setValue(0);
Animated.timing(pop, {toValue: 1, duration: 300, useNativeDriver: true}).start();
};
const scale = pop.interpolate({inputRange: [0, 0.5, 1], outputRange: [1, 1.4, 1]});
const color = pop.interpolate({
inputRange: [0, 1],
outputRange: ['rgb(200,200,200)', 'rgb(224,36,94)'],
});
// Not native-driveable by default: {width: pop.interpolate(...)}
return (
<Animated.View
onTouchEnd={play}
style={[styles.badge, {transform: [{scale}], backgroundColor: color}]}
/>
);
}
const styles = StyleSheet.create({
badge: {width: 40, height: 40, borderRadius: 20},
});go deeper
Recall the rule of thumb: the native driver handles non-layout styles like transform and opacity, not width, height or margins.
Know the actual allowlist, including colours and border radii, the error for unsupported styles, and the one-driver-per-value rule.
Translate layout animations into transforms, decide when JS driving or LayoutAnimation is acceptable, and keep drivers consistent per value.
Judge whether to adopt the experimental animation backend or a worklet-based library for layout animation, weighing stability against capability.
## Why there is an allowlist The native driver computes frames on the UI thread and applies them straight to native views. That works for properties a view can change **without recomputing layout**. Width, height, margins and positions feed into Yoga, React Native's layout engine; changing them means laying out the tree again, which the classic native driver does not do on its own. So `Animated` keeps an **allowlist** of style properties it will send to native. ## What is on the list in React Native 0.87 | Category | Properties | |---|---| | Transform | `translateX`, `translateY`, `scale`, `scaleX`, `scaleY`, `rotate`, `rotateX`, `rotateY`, `rotateZ`, `perspective`, `skewX`, `skewY` | | Visual | `opacity`, `filter`, `zIndex`, `elevation`, `shadowOpacity`, `shadowRadius` | | Colours | `backgroundColor`, `color`, `borderColor` and its per-side variants, `tintColor` | | Corners | `borderRadius` and its per-corner variants | Not on the list by default: `width`, `height`, `min*`/`max*`, `top`, `left`, `right`, `bottom`, `margin*`, `padding*`, `flex*`, `borderWidth`, `gap`. "Only `transform` and `opacity`" is the common short answer, and it undersells the list: colours and corner radii are native-driveable too. ## What happens with an unsupported property - `Animated` validates the styles of a native-driven animated component and logs an **error** of the form "Style property 'width' is not supported by native animated module". - The fix is almost always to express the same visual change with a supported property: - a heart that "grows" → `transform: [{scale}]`; - a panel that slides in → `transform: [{translateY}]` rather than animating `top`; - a bar that fills → `transform: [{scaleX}]` with a suitable origin, rather than `width`. - If the layout really must change, run the animation with `useNativeDriver: false`, accepting that JavaScript computes each frame, or use `LayoutAnimation` for the next layout change. ## One driver per value A value that has run a native-driven animation lives on the native side. Starting a **JS-driven** animation on the same value throws an error saying you are attempting to run a JS driven animation on an animated node that has been moved to "native". Keep every animation on a given value on the same driver, and use separate values when one property needs the JS driver. ## Animated.event and the native driver `Animated.event` maps event data onto values. With `useNativeDriver: true` it works only with **direct** events attached to an animated component, such as `onScroll` on an `Animated.ScrollView`. It does not work with bubbling events or with JavaScript callbacks such as `PanResponder`'s, where it must stay on the JS driver. ## The experimental backend React Native 0.85 introduced a **Shared Animation Backend**, built with Software Mansion, available from 0.85.1 on the **experimental release channel**. With it, the allowlist grows to include dimensions, positions, flex, margins, padding, border widths and gaps, so layout props can be native-driven. In a stable 0.87 app, which also covers Expo SDK 57 on 0.86, treat layout props as unsupported. ## A checklist before setting useNativeDriver: true 1. Every animated style on the component is on the allowlist (transform, opacity, colours, radii and the rest above). 2. No other animation on the same value uses the JS driver, now or later in the component's life. 3. Any `Animated.event` feeding the value comes from a direct native event on an animated component. 4. JavaScript does not need the value on every frame; if it does, a listener sends updates back at a cost. 5. Interpolations feeding the style only use supported parameters, such as `inputRange`, `outputRange`, `extrapolate` and `easing`. ## Why interviewers ask this - It separates people who have only copied `useNativeDriver: true` from people who know what it can carry. - It leads straight into the design question of how to fake a layout change with transforms. - It tests awareness that the allowlist has grown over time and that the experimental backend changes the answer, so "it depends on the version and the flags" is the accurate framing.
- In React Native Animated, how do you animate a panel's height when the native driver cannot animate height?First try to express it as a transform: slide the panel with `translateY` or grow it with `scaleY`, both native-driveable. If content must truly reflow, run a JS-driven animation with `useNativeDriver: false` and accept that busy JavaScript can make it stutter, or call `LayoutAnimation.configureNext` before changing the height in state.
- Why does starting a JS-driven animation on a value that was native-driven earlier throw in React Native?A value that has run a native-driven animation is moved to the native side, where the UI thread owns its updates. A JS-driven animation would compute frames in JavaScript for a node JavaScript no longer controls, so `Animated` throws. Keep all animations on a value on one driver, or use separate values.
saying these in an interview costs you the question
- The native driver can animate only transform and opacity, nothing else
- Native-driving width works, it just needs a layout pass per frame
- One Animated.Value can switch freely between the native and JS drivers
- Animated.event with useNativeDriver: true works inside PanResponder callbacks
- Layout props are native-driveable in every React Native 0.85+ app