Accordion
Free · MITReact Native · ExpoCollapsible rows whose panels open from their measured height on the UI thread, with content that drops into place and a chevron that turns on the same spring.
Installation
$ npx shadcn@latest add https://goodui.dev/native/r/accordion.jsonThen run npx expo install --fix so react-native-reanimated, react-native-svg match your Expo SDK. Files land in components/goodui-native/accordion.
Usage
import { Accordion } from "@/components/goodui-native/accordion/accordion";
import { Text } from "@/components/goodui-native/foundation/text";
export function Faq() {
return (
<Accordion
accessibilityLabel="Questions"
defaultValue={["refund"]}
items={[
{
value: "refund",
title: "Can I get a refund?",
content: <Text tone="secondary">Yes, within 14 days of purchase, no questions asked.</Text>,
},
{
value: "seats",
title: "How do seats work?",
subtitle: "Teams plan",
content: <Text tone="secondary">Each seat is one person. Add or remove seats at any time.</Text>,
},
]}
/>
);
}When to use
- FAQs, order details, plan comparisons and settings groups where most rows stay closed.
- Long forms split into sections that people open one at a time (type="single").
- Receipts and breakdowns where a summary row hides line items.
- Switching between two to five views of the same data: use segmented-control.
- A short choice list or picker: use a bottom-sheet with rows.
- Content everyone needs to read: show it, do not hide it behind a tap.
Examples
Controlled, several open at once, card per row
const [open, setOpen] = useState<string[]>([]);
<Accordion
type="multiple"
variant="inset"
value={open}
onValueChange={setOpen}
items={orders.map((o) => ({
value: o.id,
title: o.title,
detail: o.total,
content: <OrderLines order={o} />,
}))}
/>A section that must stay open
<Accordion type="single" collapsible={false} defaultValue={["shipping"]} items={checkoutSteps} />API reference
| Prop | Type | Default |
|---|---|---|
| items* Rows, top to bottom. | AccordionItem[] | — |
| type `single` keeps one row open at a time; `multiple` lets rows open independently. | "single" | "multiple" | "single" |
| value Open row values (controlled). Pair with `onValueChange`. | string[] | — |
| defaultValue Open row values on first render (uncontrolled). | string[] | [] |
| onValueChange Called with every open row value after a row opens or closes. | (value: string[]) => void | — |
| collapsible With `type="single"`, tapping the open row closes it. Default `true`. | boolean | true |
| variant `grouped` puts rows on one rounded surface with hairlines; `inset` gives every row its own card. | "grouped" | "inset" | "grouped" |
| size `md` rows are 52 pt with body titles; `sm` rows are 44 pt with subhead titles. | "sm" | "md" | "md" |
| haptic Haptic when a row opens or closes. `false` turns it off. | HapticName | false | "select" |
| accessibilityLabel Label for the whole group, read before the first row. | string | — |
| style Style for the outer container. | StyleProp<ViewStyle> | — |
| testID Identifier for end-to-end tests. | string | — |
Motion and haptics
- The panel's height is measured from its content and animated on the UI thread with the smooth spring.
- Content drops 8 pt into place and fades in on open; it fades out quickly on close.
- The chevron rotates 180 degrees on the same spring, so it lands with the panel.
- Content that changes height while open eases to its new height.
- Reduced motion: the height changes at once, the chevron flips without turning, and the content only fades.
- Haptic: select when a row opens or closes (configurable with the haptic prop)
- Height, offset, opacity and rotation run on the UI thread; React renders only when a row opens or closes.
- Panels mount on open and unmount after they close, so a long list of closed rows renders headers only.
Accessibility
- Each header is a button with accessibilityState expanded and disabled.
- The accessibility label joins title, subtitle and detail.
- A closed panel is hidden from screen readers and, after it closes, unmounted, so it is never reachable by keyboard on the web.
- Titles and subtitles wrap to two lines, which keeps rows readable at large font sizes.
- On the web each header is named by its visible title, subtitle and detail, and reports aria-expanded.
Notes for AI
- Pass rows through items; each item needs a unique value and its panel in content.
- value is always a string array, also with type="single".
- Use variant="grouped" inside a screen's list and variant="inset" when rows should read as separate cards.
- Do not wrap the accordion in another card: grouped already draws its own surface.
Related
Two to five views of the same thing with an elastic highlight that stretches toward the new segment, can be dragged, and ticks at every boundary. Includes a billing toggle.
A sheet that rests at detents, follows the finger 1:1, settles with velocity and hands drags off to its scroll view.
Loading placeholders whose shimmer is one light passing diagonally over the whole screen, plus presets and a Swap that eases from the skeleton's height to the content's while it cross-fades.