To collapse a section in React Native, keep an open flag in state and render the body only while it is true. Call LayoutAnimation.configureNext before changing the flag and the surrounding views slide to their new positions. Give the header Pressable accessibilityRole="button" and accessibilityState={{ expanded }} so screen readers announce whether it is open.
| Method | Animates | Good for |
|---|---|---|
| LayoutAnimation.configureNext | The next layout change of every view | Bodies of unknown height, lists of sections |
| Animated.timing with useNativeDriver | Non-layout properties such as transform and opacity | The chevron rotation and fades |
| Animated with height | Height, on the JS thread | Fixed-height panels when you need fine control |
Updated
The accordion below is the web twin of the React Native code on this page: same state shape, same expanded flag on each header. Switch between letting several sections stay open and allowing only one, then copy the version that fits your screen.
Sample help questions from a made-up store, to show the behaviour.
The body mounts only while open. The chevron turns with the native driver because rotation is a transform.
import { useEffect, useRef, useState } from "react";
import {
Animated, LayoutAnimation, Platform, Pressable, StyleSheet, Text, UIManager, View,
} from "react-native";
if (Platform.OS === "android" && UIManager.setLayoutAnimationEnabledExperimental) {
UIManager.setLayoutAnimationEnabledExperimental(true);
}
export function Collapsible({ title, open, onToggle, children }) {
const turn = useRef(new Animated.Value(open ? 1 : 0)).current;
useEffect(() => {
Animated.timing(turn, { toValue: open ? 1 : 0, duration: 200, useNativeDriver: true }).start();
}, [open, turn]);
const rotate = turn.interpolate({ inputRange: [0, 1], outputRange: ["0deg", "180deg"] });
const press = () => {
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
onToggle();
};
return (
<View style={s.item}>
<Pressable
accessibilityRole="button"
accessibilityState={{ expanded: open }}
onPress={press}
style={s.header}
>
<Text style={s.title}>{title}</Text>
<Animated.Text style={{ transform: [{ rotate }] }}>⌄</Animated.Text>
</Pressable>
{open ? <View style={s.body}>{children}</View> : null}
</View>
);
}
const s = StyleSheet.create({
item: { borderBottomWidth: 1, borderColor: "#e5e7eb" },
header: { flexDirection: "row", justifyContent: "space-between", padding: 16 },
title: { fontWeight: "600", flexShrink: 1 },
body: { paddingHorizontal: 16, paddingBottom: 16 },
});A single id in state. Pressing the open section again sets it to null and closes it.
export function Accordion({ items }) {
const [openId, setOpenId] = useState(null);
return items.map((item) => (
<Collapsible
key={item.id}
title={item.title}
open={openId === item.id}
onToggle={() => setOpenId((cur) => (cur === item.id ? null : item.id))}
>
<Text>{item.body}</Text>
</Collapsible>
));
}
An accordion keeps one section open, which suits settings screens and short FAQs where the reader wants one answer. Independent sections suit filters or order details, where comparing two open panels is useful.
Both use the same component; only the toggle function changes. Store a Set of ids for independent sections, or a single id that becomes null when the open one is pressed again.
LayoutAnimation configures only the very next layout, so call it in the press handler, immediately before setState, every time. Calling it once at mount has no effect on later presses.
Very long bodies can still stutter on low-end phones. Keep heavy lists inside a section lazy, or render a short preview with a link to a full screen.
Call LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut) right before the state change that shows or hides the body. The easeInEaseOut preset runs for 300 milliseconds.
The LayoutAnimation page says Android needs UIManager.setLayoutAnimationEnabledExperimental(true), called only if that function exists. Run it once when the app starts, before the first animated change.
No. The React Native docs limit the native driver to non-layout properties such as transform and opacity, so layout values like height are out. Animate height without the native driver, or use LayoutAnimation instead.
Set accessibilityState={{ expanded: isOpen }} on the header, or the aria-expanded prop. The docs describe expanded as whether an expandable element is currently expanded or collapsed.
Related: React Native radio buttons, React Native dropdown, aria-label examples, React hooks cheat sheet, React vs React Native.
Start for free or see all free tools. We.Inc is free to start, with no card needed. AI building is on paid plans from $20 a month.
We.Inc is an AI-powered website builder you can resell under your own brand. Launch a branded client dashboard, bill on Stripe Connect, and deliver AI-generated websites in minutes. White-label plans start at $99 a month for 25 client sites, with a 7-day free trial and no per-site fees.