Animations

Morphing Dialog

Pronew

A card that grows into a dialog (its image, title and subtitle travel with it) and shrinks back on close.

Installation

npx shadcn@latest add @blank-pro/morphing-dialog

Needs a Blank Pro licence key and the @blank-pro registry. Set up Pro access

Usage

import {
  MorphingDialog,
  MorphingDialogClose,
  MorphingDialogContainer,
  MorphingDialogContent,
  MorphingDialogDescription,
  MorphingDialogImage,
  MorphingDialogSubtitle,
  MorphingDialogTitle,
  MorphingDialogTrigger,
} from "@/components/ui/morphing-dialog"
<MorphingDialog>
  <MorphingDialogTrigger>
    <MorphingDialogImage src="/lake.jpg" alt="" />
    <MorphingDialogTitle>Moraine Lake</MorphingDialogTitle>
    <MorphingDialogSubtitle>Banff, Alberta</MorphingDialogSubtitle>
  </MorphingDialogTrigger>
  <MorphingDialogContainer>
    <MorphingDialogContent>
      <MorphingDialogImage src="/lake.jpg" alt="" />
      <MorphingDialogTitle>Moraine Lake</MorphingDialogTitle>
      <MorphingDialogSubtitle>Banff, Alberta</MorphingDialogSubtitle>
      <MorphingDialogDescription>…</MorphingDialogDescription>
      <MorphingDialogClose />
    </MorphingDialogContent>
  </MorphingDialogContainer>
</MorphingDialog>

Each part in the trigger has a twin in the content. The surface, image, title and subtitle share a layout id with their twin, so motion animates one into the other; MorphingDialogDescription exists only in the dialog and fades in once the surface has grown.

The dialog is Radix Dialog underneath. Focus moves into it and returns to the card on close; Escape, a click on the backdrop and the close button dismiss it; the page stops scrolling. Inside the content, MorphingDialogTitle becomes the dialog's accessible title and MorphingDialogDescription its description — the same component in the card stays plain text.

Text morphs by position only, so a title can change size between card and dialog without stretching. With prefers-reduced-motion, the shared layout animation is replaced by a short cross-fade.

Examples

One MorphingDialog per card. Layout ids are scoped to each instance.

Without media

Any card can morph — here a row turns into a list of shortcuts.

API Reference

MorphingDialog

PropTypeDefault
openboolean—
defaultOpenbooleanfalse
onOpenChange(open: boolean) => void—
transitionTransition{ type: "spring", bounce: 0.05, duration: 0.35 }

MorphingDialogTrigger, MorphingDialogContent

Motion button and div props. Both default to bg-card with a border; the corner radius is set through style (20 and 24) because motion corrects an inline radius for scale during the morph.

MorphingDialogContainer

PropTypeDefault
classNamestring—

Renders the portal, the backdrop and a centring frame (p-4).

MorphingDialogImage

Motion img props; src and alt are required.

MorphingDialogTitle, MorphingDialogSubtitle

Motion div props.

MorphingDialogDescription

PropTypeDefault
variants{ initial, animate, exit }fade, rise and unblur

MorphingDialogClose

Motion button props. Renders an X when it has no children.

useMorphingDialog

Returns { open, setOpen } for parts you build yourself — a "Next" button inside the content, for example.