Accordion

Free · MITReact Native · Expo

Collapsible 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.

View as Markdown
Try it on your phone
Scan to open this demo in your phone's browser and use it with your fingers.

Installation

$ npx shadcn@latest add https://goodui.dev/native/r/accordion.json

Then 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

Use it for
  • 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.
Reach for something else
  • 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

PropTypeDefault
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`.
booleantrue
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.