All components

MotionSwap

Content that cross-fades while its size follows.

WebReact NativeContent and size

Try it

Preparing the example…

Shortest usage

SaveButton.jsx
<button onClick={save}>
  <MotionSwap value={status} effect="up">
    {status === 'saving' ? 'Saving…' : status === 'saved' ? 'Saved' : 'Save'}
  </MotionSwap>
</button>;

What it handles for you

  • The element resizes from its size on screen to the new content's natural size, so buttons never jump.
  • The old content fades out where it was while the new content fades in; neither reflows during the swap.
  • Changing back in the middle of a swap continues from what is on screen instead of stacking copies.
  • Plain text children are their own value; pass value for anything else.

Use it for

  • Save, then Saving…, then Saved.
  • A skeleton that becomes a profile card.
  • A play button that becomes pause.

Props

PropTypeDefaultDescription
valueunknownIdentity of the content; a new value swaps. Plain text children are their own identity.
childrenReactNodeThe content.
aselement'span'The element to render.
inlinebooleanas === 'span'true sizes to the content (labels, icons); false fills the parent width and animates height.
align'start' | 'center' | 'end''center'Where the content sits while the size changes.
effect'fade' | 'up' | 'down' | 'scale''fade'How content enters and leaves: up rises, down falls, scale grows.
transitionTransitionspringA timing curve { duration, easing } or a spring { tempo: 'spring', spring }. Springs keep their momentum when interrupted.
onMotionComplete(value) => voidRuns when the motion settles.

Other props go to the rendered element, so className, style and event handlers work as usual.

Web and React Native

inline (the default when rendered as a span) animates width and height; inline={false} fills the parent width and animates height.

On React Native, inline swaps measure the new content on one line; use inline={false} for content that wraps.

Used in these examples

Search

Type to search the documentation.