About this animation
A floating action button that becomes an editable card while the screen recedes behind it.
Why this one
The button and card are one continuous surface rather than two views crossfading. The same interruptible progress value drives its position, dimensions, color, backdrop depth, icon, content, and drag-to-dismiss interaction. The expanded card also follows the software keyboard, preserving a 24-point gap instead of letting the form become obscured.
How it works
- The component measures its own container, so the closed button and open card adapt to split screens, tablets, and nested layouts.
- A single
progressshared value springs between zero and one and interpolates the surface's position, width, height, radius, and background color. - The background scales inside a rounded clipping view while a separate dimming veil fades in.
- Card content waits until 70% of the shape transition is complete, then fades and rises into place.
- A downward pan writes directly to the same progress value; release velocity and position decide whether to close or spring back open.
- Reanimated's keyboard height keeps the card's lower edge 24 points above the keyboard.
Install
npx expo install react-native-gesture-handler react-native-reanimated react-native-workletsUsage
import MorphingActionButton from './MorphingActionButton';
export function NotesScreen() {
return (
<MorphingActionButton background={<NotesList />}>
<NoteForm />
</MorphingActionButton>
);
}Customize the surface and control it through the forwarded ref:
import { useRef } from 'react';
import MorphingActionButton, {
type MorphingActionButtonRef,
} from './MorphingActionButton';
export function Composer() {
const actionRef = useRef<MorphingActionButtonRef>(null);
return (
<MorphingActionButton
ref={actionRef}
background={<Dashboard />}
buttonColor="#20202A"
cardColor="#111827"
cardWidth={0.92}
cardHeight={340}
backdropScale={0.93}
onOpenChange={(open) => trackComposer(open)}
accessibilityLabel="Compose"
>
<ComposerForm onDone={() => actionRef.current?.close()} />
</MorphingActionButton>
);
}The forwarded ref exposes open() and close().
Props
| Prop | Type | Default | Description |
|---|---|---|---|
children | React.ReactNode | Required | Content displayed inside the expanded card. |
background | React.ReactNode | — | Screen content behind the transforming surface. |
buttonColor | string | '#111111' | Collapsed button color. |
cardColor | string | '#16161C' | Expanded card color. |
cardWidth | number | 0.88 | Expanded width as a fraction of the container. |
cardHeight | number | 300 | Expanded height in points. |
backdropScale | number | 0.95 | Background scale while open. |
onOpenChange | (open: boolean) => void | — | Called when opening or closing starts. |
accessibilityLabel | string | 'Create' | Label for the floating action and expanded card. |
style | StyleProp<ViewStyle> | — | Outer-container style. |
testID | string | — | Test identifier for the outer container. |
Compatibility
- Expo SDK 57
- React Native 0.86
- Reanimated 4.5 and Worklets 0.10
- iOS: supported, not yet verified on a device
- Android: supported, not yet verified on a device
- Web: supported, not yet verified
- Works in Expo Go because its native dependencies are included in the SDK
Accessibility
The action, close control, backdrop dismissal, form controls, and save action expose labels and roles. Opening moves screen-reader focus to the expanded card, and the close button provides a tap alternative to the downward gesture. With reduced motion enabled, opening, closing, and gesture settling snap directly to their destination while the keyboard avoidance remains active.