Expandable

A card that morphs into a full view, App Store style. Shared elements — media, title, subtitle — fly to their new place while the rest fades in.

Overlays

Playground

Tweak the props, the preview and the code follow.

Props
0.12
0.5
330
<Expandable transition={{ type: "spring", bounce: 0.12, duration: 0.5 }}>
  <ExpandableTrigger>
    <ExpandableMedia>…</ExpandableMedia>
    <ExpandableTitle>Spring tuning</ExpandableTitle>
    <ExpandableSubtitle>Click to expand</ExpandableSubtitle>
  </ExpandableTrigger>
  <ExpandableContent>
    <ExpandableClose />
    <ExpandableMedia>…</ExpandableMedia>
    <ExpandableTitle>Spring tuning</ExpandableTitle>
    <ExpandableSubtitle>…</ExpandableSubtitle>
    <ExpandableDescription>Only rendered when open.</ExpandableDescription>
  </ExpandableContent>
</Expandable>

Installation

npx @plumsydev/plum-ui add expandable

Usage

import {
  Expandable,
  ExpandableClose,
  ExpandableContent,
  ExpandableDescription,
  ExpandableMedia,
  ExpandableSubtitle,
  ExpandableTitle,
  ExpandableTrigger,
} from "@/components/ui/expandable"
<Expandable>
  <ExpandableTrigger>
    <ExpandableMedia>…</ExpandableMedia>
    <ExpandableTitle>acme-storefront</ExpandableTitle>
    <ExpandableSubtitle>Next.js</ExpandableSubtitle>
  </ExpandableTrigger>
  <ExpandableContent>
    <ExpandableClose />
    <ExpandableMedia>…</ExpandableMedia>
    <ExpandableTitle>acme-storefront</ExpandableTitle>
    <ExpandableSubtitle>Next.js</ExpandableSubtitle>
    <ExpandableDescription>Only rendered when open.</ExpandableDescription>
  </ExpandableContent>
</Expandable>

Examples

Each card in a grid expands on its own.

Profile

Any shape works: a pill morphs into a profile card.

API Reference

Expandable

PropTypeDefault
open

Controlled open state.

boolean—
defaultOpen

Initial state when uncontrolled.

booleanfalse
onOpenChange

Called on open and close.

(open: boolean) => void—
transition

Motion transition used for the morph.

Transition{ type: "spring", bounce: 0.12, duration: 0.5 }

ExpandableMedia / Title / Subtitle

Shared elements. Render them in both the trigger and the content: they morph between the two.

ExpandableDescription

Content that only exists in the open view. Fades in after the morph.

Notes

  • The open view is a real dialog: role="dialog", aria-modal, labelled by its title, Esc to close, focus kept inside and returned to the card.
  • Titles and subtitles animate their position only, so text never stretches mid-flight.