Skip to content
Native Animation
Loading Morph Button animation preview

Loading Morph Button

A premium async action pill that contracts into a gradient progress arc and resolves with drawn success or tactile retry feedback.

$9 for this animation

Checkout opens soon. Leave your email and we will tell you the moment you can buy it.

One email when checkout opens. No spam.

Add to your project

Copy the component and its assets into your app with the Native Animation CLI, or paste the agent prompt into Claude Code, Codex or Cursor once the MCP server is connected.

npx nativeanimation add loading-morph-button

About this animation

A premium async action pill that contracts into a gradient progress arc and resolves with drawn success or tactile retry feedback.

Why this one

Most loading buttons swap text for a stock spinner. This version treats the surface as one continuous object: the label lifts away with a soft blur, the pill springs into a circle, and a fine gradient-tailed arc appears only after the shape is ready. Success expands before drawing its check and releasing a single halo; failure expands into a muted error surface with a three-oscillation shake and a clear “Try again” action. Controlled state and the imperative reset can interrupt every transition.

How it works

  1. The uncontrolled component awaits onPress; a request token ignores stale promises after reset.
  2. A shared morph value springs between the full pill and its circular loading diameter.
  3. A continuous Skia path uses a sweep-gradient trail and UI-thread rotation.
  4. Success draws a Skia check and releases one expanding outline halo.
  5. Error uses a shrinking three-cycle horizontal shake and exposes “Try again.”
  6. Reduced Motion keeps state feedback while removing rotation, travel, shake, and celebration.

Install

npx expo install expo-linear-gradient @shopify/react-native-skia react-native-reanimated

Usage

<LoadingMorphButton label="Pay $128.00" onPress={submitPayment} />

<LoadingMorphButton
  label="Place order"
  width={260}
  height={56}
  color="#25232C"
  successColor="#439B76"
  errorColor="#B85D5B"
  resetMs={2200}
  onPress={submitOrder}
/>

Props

PropTypeDefaultDescription
state'idle' | 'loading' | 'success' | 'error'—Controlled visual state.
defaultStateState'idle'Initial uncontrolled state.
onPress() => Promise<unknown>RequiredResolve for success or reject for error.
onStateChange(state) => void—Receives requested states.
labelstring'Continue'Idle action text.
widthnumber220Full pill width.
heightnumber54Pill height and loading diameter.
colorstring'#19191F'Idle/loading surface color.
surfaceHighlightColorstringScheme-awareTop material highlight.
successColorstring'#3F9D78'Success surface and halo color.
errorColorstring'#B85D5B'Error surface color.
contentColorstring'#FFFFFF'Label, arc, and check color.
resetMsnumber1800Uncontrolled result display duration.
fontFamilystringSystem fontButton label font.
colorScheme'light' | 'dark' | 'auto''auto'Material treatment.
accessibilityLabelstringUses labelScreen-reader label.
styleStyleProp<ViewStyle>—Outer-area style.
testIDstring—Test identifier.

The forwarded ref exposes press() and reset().

Compatibility

Built for Expo SDK 57, React Native 0.86, Reanimated 4.5, and React Native Skia 2.6. iOS and Android are supported but device-unverified; web is verified by the project gate. Works in Expo Go.

Accessibility

The pressable exposes button, busy, disabled, and contextual retry labels. Its touch target stays at full width while the surface contracts. Reduced Motion removes travel, rotation, shake, spring bounce, and the halo while retaining immediate shape, color, check, and text feedback.