How to build an accordion in React Native

By the LumioUI team · Updated

An accordion is a list of sections that expand in place to show more. In React Native, measure each section's content, then animate the container's height from 0 to the measured height with Reanimated while the chevron rotates. Mark the header expanded for screen readers. The LumioUI Accordion grows each section in place and gently pushes the content below.

Live demo of the LumioUI Accordion component. It's interactive, so try it.

How an accordion animates

  1. Measure the body. React Native can't animate to height: "auto", so render the content off-flow once and read its height with onLayout.
  2. Animate one progress value. Height and chevron rotation both come from the same 0 to 1 value, so they always agree. Spring it open and close it a little faster.
  3. Clip, don't scale. Use overflow: "hidden" on the animated container so the text is revealed, not squashed.
  4. Single or multiple. FAQs usually allow one open section at a time, while settings screens often allow several. Hold the open keys in state either way.
  5. Accessibility. The header is a button with accessibilityState.expanded, so screen readers say "collapsed" or "expanded".
TSX Section.tsx
import * as React from "react";
import { Pressable, Text, View } from "react-native";
import Animated, { useAnimatedStyle, useSharedValue, withSpring, withTiming } from "react-native-reanimated";

export function Section({ title, children }: { title: string; children: React.ReactNode }) {
  const [open, setOpen] = React.useState(false);
  const contentH = useSharedValue(0);   // measured height of the body
  const progress = useSharedValue(0);   // 0 closed → 1 open

  const toggle = () => {
    setOpen((o) => !o);
    progress.value = open ? withTiming(0, { duration: 180 }) : withSpring(1, { damping: 20, stiffness: 220 });
  };

  const body = useAnimatedStyle(() => ({ height: contentH.value * progress.value, overflow: "hidden" }));
  const chevron = useAnimatedStyle(() => ({ transform: [{ rotate: `${progress.value * 180}deg` }] }));

  return (
    <View>
      <Pressable onPress={toggle} accessibilityRole="button" accessibilityState={{ expanded: open }}>
        <Text>{title}</Text>
        <Animated.Text style={chevron}>⌄</Animated.Text>
      </Pressable>
      <Animated.View style={body}>
        {/* Measured off-flow so the height is known before opening */}
        <View style={{ position: "absolute", width: "100%" }} onLayout={(e) => (contentH.value = e.nativeEvent.layout.height)}>
          {children}
        </View>
      </Animated.View>
    </View>
  );
}

The quick way: LumioUI Accordion

The Accordion handles measuring, motion, reduced motion and accessibility. Open sections also round their corners, so a group reads as one surface. It's free and runs in Expo Go.

TSX Faq.tsx
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion/accordion";
import { Text } from "@/components/ui/text/text";

export function Faq() {
  return (
    <Accordion type="single" collapsible defaultValue={["refunds"]}>
      <AccordionItem value="refunds">
        <AccordionTrigger>Can I get a refund?</AccordionTrigger>
        <AccordionContent><Text>Yes, within 14 days of purchase.</Text></AccordionContent>
      </AccordionItem>
      <AccordionItem value="devices">
        <AccordionTrigger>How many devices can I use?</AccordionTrigger>
        <AccordionContent><Text>As many as you like on one account.</Text></AccordionContent>
      </AccordionItem>
    </Accordion>
  );
}
  • type="single" (the default) or "multiple". collapsible lets the open section close again.
  • value, defaultValue and onValueChange take arrays of item values.
  • lit highlights the section you open. AccordionItem takes disabled.
  • For one standalone expanding block, use Collapsible.

Add it to your app

LumioUI's CLI copies the component's source into your project, so it's yours to read and change. Run init once per project:

npx @lumioui/cli init
npx @lumioui/cli add accordion

It works in Expo (SDK 54+) and bare React Native, with Reanimated 4 and NativeWind. See the installation guide and the full Accordion API.

More React Native guides