# Expandable card

> A card that opens into its detail: inline it grows from its own measured height while the preview cross-fades into the detail under a header that never moves; fullscreen the card itself lifts out and stretches to fill the screen, and a swipe down shrinks it back into its place.

- Type: Component (data)
- Access: Free, open source (MIT)
- Page: https://goodui.dev/native/components/expandable-card
- Markdown: https://goodui.dev/native/components/expandable-card.md
- Registry item: https://goodui.dev/native/r/expandable-card.json
- Dependencies: react-native-gesture-handler, react-native-reanimated, react-native-svg
- Keywords: react native expandable card, expand card animation, shared element transition, card to fullscreen, app store card, collapsible card, swipe down to dismiss, expo card

## When to use

- Summaries in a list that hold more detail: a booking, a transaction, an order receipt (inline).
- Feature or content cards that open into a full page of their own: a workout plan, a story, a trip (fullscreen).
- When the detail should feel like the same object, not a new screen pushed on top.

## When not to use

- A stack of plain text rows that open and close: use accordion.
- Actions or options for an item: use action-sheet or context-menu.
- Detail that needs its own navigation history or deep links: push a screen.
- Content that rises from the bottom with detents: use bottom-sheet.

## Installation

With the shadcn CLI (needs a components.json in the Expo app), then `npx expo install --fix` so native dependencies match your Expo SDK. The foundation comes along the first time.

```bash
npx shadcn@latest add https://goodui.dev/native/r/expandable-card.json
npx expo install --fix
```

The component lands in `components/goodui-native/expandable-card/` and imports the foundation from `components/goodui-native/foundation/` with relative paths; it also installs locale.

Built-in strings follow `<GooduiLocaleProvider>` (one [language pack](https://goodui.dev/native/r/locale-tr.json) translates the whole kit); the `labels` prop overrides them for one instance.

## Usage

```tsx
import { Text, View } from "react-native";
import { ExpandableCard } from "@/components/goodui-native/expandable-card/expandable-card";

export function BookingCard() {
  return (
    <ExpandableCard
      accessibilityLabel="Massage, Thursday 14:30"
      header={
        <View>
          <Text>Deep tissue massage</Text>
          <Text>Thu 14:30 · Studio Alma</Text>
        </View>
      }
      preview={<Text>60 min with Rita · €70, paid</Text>}
      detail={
        <View style={{ gap: 12 }}>
          <Text>Therapist: Rita Sousa</Text>
          <Text>Address: Rua da Rosa 12, Lisbon</Text>
        </View>
      }
    />
  );
}
```

## Examples

### Fullscreen with swipe down to close

```tsx
<ExpandableCard
  mode="fullscreen"
  accessibilityLabel="Leg day, 55 minutes"
  header={<WorkoutHero workout={w} />}
  preview={<Text>Tap for the plan</Text>}
  detail={<WorkoutPlan workout={w} />}
/>
```

### Controlled

```tsx
const [open, setOpen] = useState(false);

<ExpandableCard expanded={open} onExpandedChange={setOpen} header={<OrderHeader />} detail={<Receipt />} />
<Button onPress={() => setOpen((o) => !o)}>{open ? "Hide receipt" : "Show receipt"}</Button>
```

## API reference

### ExpandableCard

`import { ExpandableCard } from "@/components/goodui-native/expandable-card/expandable-card";`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `header` (required) | `ReactNode` | – | Shown in both states and never moves: title, price, avatar. Keep it non-interactive. |
| `preview` | `ReactNode` | – | Collapsed body under the header, such as a summary line. Keep it non-interactive: the card handles the tap. |
| `detail` (required) | `ReactNode` | – | Expanded body. It may hold buttons and inputs. Mounted on open, unmounted after it closes. |
| `mode` | `"inline" \| "fullscreen"` | `"inline"` | `inline` grows in place and pushes the content below; `fullscreen` lifts out and fills the screen. |
| `expanded` | `boolean` | – | Expanded state (controlled). Pair it with `onExpandedChange`. |
| `defaultExpanded` | `boolean` | `false` | Expanded state on first render (uncontrolled). |
| `onExpandedChange` | `(expanded: boolean) => void` | – | Called when a tap, the close button, a swipe down, Escape or the back button changes it. |
| `variant` | `"filled" \| "outline" \| "elevated"` | – | `elevated` surface with a soft shadow, `outline` with a hairline, `filled` quiet fill. |
| `haptic` | `HapticName \| false` | `"tap"` | Haptic when the card opens or closes. `false` turns it off. |
| `closeLabel` | `string` | – | Label of the fullscreen close button. Same as `labels.close`. |
| `accessibilityLabel` | `string` | – | Name of the card for screen readers on native. The web reads the header's text. |
| `disabled` | `boolean` | – | Dims the control and ignores presses. |
| `labels` | `Partial<ExpandableCardLabels>` | – | Overrides for the built-in strings; a GooduiLocaleProvider pack sets them app-wide. |
| `style` | `StyleProp<ViewStyle>` | – | Style for the outer container. |
| `testID` | `string` | – | Identifier for end-to-end tests. |

## Accessibility

- Inline: the header is a button with expanded in accessibilityState and aria-expanded on the web; the preview is a second tap target hidden from screen readers. The collapsed detail is unmounted.
- Fullscreen: the whole card is a button with expanded state. The open card is a modal dialog (accessibilityViewIsModal, role dialog with aria-modal on the web) with a labelled close button, and opening is announced.
- Escape closes on the web and the back button on Android. The detail scroll area takes keyboard focus on the web so it can be scrolled with arrow keys.
- accessibilityLabel names the card on native; on the web the header's visible text names it.

## Motion

- Inline: the body's height springs on the smooth spring from the measured preview height to the measured detail height; the preview fades out over the first half while the detail fades in and drops 8 pt into place; the chevron turns. Detail that changes size while open eases to the new height.
- Fullscreen: the card's own frame (position, size and corner radius) animates on the smooth spring from its place in the list to the full screen while the scrim fades in; the header moves down under the status bar, the preview fades out, the detail rises 24 pt and fades in, the close button fades in last.
- Swipe down (with the detail scrolled to the top): the card follows at half the finger's distance, shrinks up to 12 % and rounds its corners; past 120 pt or a fast flick it shrinks back into the list, otherwise it springs back with the release velocity.
- Press: the card scales down slightly on touch down on the snappy spring.
- Reduced motion: inline height changes at once and the bodies cross-fade; fullscreen opens and closes as a fade with no travel; no press scale.

## Haptics

- tap when the card opens or closes (configurable with haptic)

## Performance

- Heights, frames and fades run on the UI thread; React state changes when the card opens, when the detail first mounts and when it unmounts.
- Inline detail mounts on open and unmounts after it closes; the fullscreen layer exists only while open or animating.

## Notes for AI

- header and preview must be non-interactive (the card handles the tap); buttons and inputs go in detail.
- Use mode='fullscreen' for rich detail pages and the default inline mode for receipts and summaries in a list.
- In fullscreen the detail renders inside a ScrollView with padding; do not wrap it in another ScrollView.
- Fullscreen measures the card on screen; it works inside ScrollViews and lists, and the card hides in place while it is open.
- Do not nest an ExpandableCard inside another card surface; place it on the screen background.

## Related

- [Accordion](https://goodui.dev/native/components/accordion.md): 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.
- [Bottom sheet](https://goodui.dev/native/components/bottom-sheet.md): A sheet that rests at detents, follows the finger 1:1, settles with velocity and hands drags off to its scroll view.
- [List row](https://goodui.dev/native/components/list-row.md): Settings and navigation cells with a token-colored icon tile, title, subtitle and a trailing value, switch, badge, check or chevron, grouped in inset sections with headers and footers. On touch down the highlight floods in, the tile sinks and the chevron leans forward; on release the highlight lingers and fades.
- [Carousel](https://goodui.dev/native/components/carousel.md): A snapping card carousel that follows the finger 1:1, shrinks and dims the neighbours, stretches its pagination pill with the drag, and turns that pill into a timer when it autoplays.

## Guidance for AI tools

Expandable card: A card that opens into its detail: inline it grows from its own measured height while the preview cross-fades into the detail under a header that never moves; fullscreen the card itself lifts out and stretches to fill the screen, and a swipe down shrinks it back into its place. Use only the documented props. Keep accessibility, reduced motion and both color schemes intact when adapting it.

Full library index: https://goodui.dev/native/llms.txt
