# Genie Form: React Native form that genies in and out of a button > Genie Form is a React Native and Expo component that grows a real, > interactive form out of a bottom-center button and sucks it back under the > button on send, using a macOS Dock style genie warp: the live card is > captured with `@shopify/react-native-skia`'s `makeImageFromView` and bent > through a 14x48 `` triangle mesh rebuilt every frame in a > Reanimated `useDerivedValue` worklet. Sold by Motionary as editable > TypeScript source. Product page: https://motionary.dev/animations/genie-form Price: $11 USD (price as of 2026-09-01, live price on the product page) License: single developer, unlimited personal and commercial apps Delivery: instant download of the full TypeScript source after checkout Stack: reanimated, expo, react native skia, react native Rarity: legendary Sold by: Motionary (https://motionary.dev) ## What it is Genie Form is a React Native form modal that is anchored to a round black "+" button pinned at the bottom center of the screen. Tap the button and a card does not fade or slide in: it squeezes up out of the thin slot under the button, its bottom edge unpinching first and the body rising behind it, so the form literally grows out of the button the way a minimized window grows out of a macOS Dock icon. Once it settles it is an ordinary React Native card with a title, two `TextInput` fields (name and message) and cancel plus send buttons, so you can type in it normally and the keyboard behaves. Press send or cancel and the same warp runs backwards: the card necks into the slot and disappears under the button, which hides the convergence point, while the "+" glyph rotates 45 degrees into an "x" and back. The genie itself is modelled on the classic macOS Dock minimize effect, rebuilt in Skia rather than borrowed from any native API. ## What it does - Warps a snapshot of the live form through a Skia `` triangle mesh of 14 columns by 48 rows (more rows than columns, because the genie motion is mostly vertical and the neck needs rows to bend smoothly). - Keeps the card fully interactive at rest: Genie Form is a real React Native view in the `form` phase and only becomes a static warped texture during the `genie` transition, swapped at progress 0 where the mesh sits pixel-perfect over the live view, so the handoff never flashes. - Runs a four-state phase machine: `idle` (only the button), `capturing` (the form is mounted but hidden behind an opaque cover while it is snapshot), `genie` (the mesh is warping, in either direction) and `form` (the live card is resting on screen). - Sweeps the neck bottom-first with a sigmoid, so the bottom edge pinches into the slot before the top does, with two eased clocks: a quadratic front and a cubic body slide. - Clamps every mesh row so it can never travel below its slot target, which means nothing pokes out from under the button mid-animation. - Bakes the card's 24 pixel corner radius into the captured texture with `clipRRect`, so the warped mesh has rounded corners instead of a hard rectangle. - Draws its drop shadow in a separate Skia canvas (`RoundedRect` plus `BlurMask`) whose x, y, width, height, corner radius and opacity are all derived from the same `progress` shared value, so the shadow fades in and out with the motion and cannot glitch during the live-view swap. - Preserves typed text across close and reopen: the name and message state is intentionally not reset, so reopening genies the filled-in card back out. - Fires three distinct haptics with `expo-haptics`: `selectionAsync` on open, `impactAsync(Medium)` on close, and `notificationAsync(Success)` when a send finishes settling. - Handles keyboard flow: the name field auto-focuses 300ms after the open animation finishes, its return key jumps to the multiline message field, and tapping the card background or the backdrop dismisses the keyboard. - Guards against double taps with a synchronous `busyRef` latch, so a second tap mid-transition is ignored until the in-flight one settles. - Disables send until the name field is non-empty, and disposes the previous `SkImage` snapshot before replacing it so textures are not leaked. - Falls back safely: if `makeImageFromView` returns nothing, Genie Form returns to `idle` on open, or stays interactive on close so the user can retry. ## How it works Genie Form lives in `components/genie/GenieForm.tsx`. The whole animation is driven by one Reanimated shared value, `progress`, where 0 means the form is open at its card rect and 1 means it is tucked under the button. A `useDerivedValue` worklet recomputes the entire mesh from that single number on the UI thread every frame: for each of the 49 rows it evaluates a sigmoid `1 / (1 + Math.exp(-(v - front) * NECK))` where `front` sweeps from 1.5 to -0.5 as `progress` rises, so rows below the front are "necked" (scaled toward the narrow slot width, `Math.min(cardW * 0.08, 26)`, and recentered on the slot) and rows above it stay wide. The vertical term blends the row's original y plus a cubic downward slide against its stacked position inside the slot with the same sigmoid, which is what produces the Dock-style neck rather than a plain scale. Those points feed a Skia `` whose `indices` array is memoized once and whose `textures` array is memoized per snapshot, with an `` supplying the captured card as the texture. The texture comes from `makeImageFromView(cardRef)`, which is why the card is a plain `View` with `collapsable={false}`: it needs its own layer to capture cleanly. `components/genie/roundSnapshot.ts` then re-bakes the corner radius into that image with `createPicture` plus `canvas.clipRRect` and `drawAsImageFromPicture`, with `captureRounded` scaling `CARD_RADIUS` by the snapshot's pixel ratio (`image.width() / cardW`) before it is clipped. Opening is the awkward direction, because the snapshot has to exist before the user sees anything, so `openGenie` mounts the form behind an opaque white cover, waits two `requestAnimationFrame` ticks for layout, captures, hides the live view by setting `formIn` to 0, drops the cover and only then runs `progress.set(withTiming(0, { duration: 700, easing: Easing.out(Easing.cubic) }))`. Closing is the mirror: capture the live card, mount the mesh at `progress` 0 directly over it, then time to 1 in 680ms with `Easing.in(Easing.cubic)`. Both `withTiming` callbacks are worklets, so `scheduleOnRN` from `react-native-worklets` is what hands control back to JavaScript to flip the phase, clear the busy latch and fire haptics. Nothing about the warp touches the JavaScript thread while it is running, and the Skia mesh layer sits under `pointerEvents="none"` so the live form underneath stays touchable. ## What you get ``` App.tsx index.ts components/ Container.tsx genie/ GenieForm.tsx roundSnapshot.ts assets/ icon.png adaptive-icon.png splash-icon.png app.json babel.config.js package.json tsconfig.json README.md ``` - TypeScript source (`GenieForm.tsx`), the button, the form, the snapshot capture and the genie mesh - `roundSnapshot` helper, the rounded-corner texture baker - Demo screen (`Container.tsx` plus `App.tsx` with the providers wired) - README with install and usage - MP4 preview ## Requirements Genie Form is pinned in its `package.json` to: - `expo` ^56.0.4 (Expo SDK 56) - `react-native` 0.85.3 - `react` 19.2.3 - `@shopify/react-native-skia` 2.6.2 (the snapshot, mesh and shadow) - `react-native-reanimated` 4.3.1 (the `progress` shared value and worklets) - `react-native-worklets` 0.8.3 (`scheduleOnRN`) - `react-native-gesture-handler` ~2.31.1 (the demo `App.tsx` root view; `GenieForm` itself uses React Native `Pressable`) - `react-native-safe-area-context` ~5.7.0 (button and card insets) - `expo-haptics` ~56.0.3 (open, close and send feedback) - `@expo/vector-icons` ^15.0.3 (the Feather "plus" glyph) - `expo-status-bar` ~56.0.4, `expo-asset` ~56.0.14, `expo-font` ~56.0.5 (the demo project's Expo scaffolding, not imported by `GenieForm`) - TypeScript ~6.0.3 (devDependency) Genie Form runs in Expo Go on Expo SDK 56. `@shopify/react-native-skia`, the dependency people assume rules Expo Go out, is bundled in Expo Go as of SDK 56, and so is everything else Genie Form imports (`react-native-reanimated`, `react-native-worklets`, `react-native-safe-area-context`, `expo-haptics` and `@expo/vector-icons`). Run `npx expo start` and open the project in Expo Go. A development build is still what you would ship to production, and you need one (`npx expo run:ios` or `npx expo run:android`) as soon as you add a native module that Expo Go does not bundle, or a config plugin of your own. Everything else is standard Expo SDK 56, and the drop is tested on both iOS and Android. Note that Reanimated 4 needs `babel-preset-expo`, which wires the `react-native-worklets` Babel plugin automatically when the package is installed; the included `babel.config.js` shows exactly that setup. ## Install ```bash npx expo install @shopify/react-native-skia react-native-reanimated react-native-worklets react-native-safe-area-context expo-haptics @expo/vector-icons ``` ## Usage ```tsx import { View } from 'react-native'; import { SafeAreaProvider } from 'react-native-safe-area-context'; import { GenieForm } from './components/genie/GenieForm'; export default function App() { return ( {/* GenieForm fills its parent and positions itself: the bottom-center button, the live card, the Skia shadow canvas and the genie mesh are all absolutely placed inside this container. */} ); } ``` `GenieForm` takes no props. `SafeAreaProvider` is required, because Genie Form reads `useSafeAreaInsets` to place the button and the card. The shipped `App.tsx` also wraps the screen in `GestureHandlerRootView` and an `expo-status-bar` `StatusBar`, which come from the Expo template; `GenieForm` imports neither. ## Customization `GenieForm` from Motionary takes no props: it is a self-contained screen component you edit directly. The tuning dials are named constants at the top of `components/genie/GenieForm.tsx`: - `COLS` (`14`) and `ROWS` (`48`): mesh resolution. More rows means a smoother neck, more columns means a smoother horizontal pinch. - `NECK` (`12`): sigmoid sharpness of the neck. Higher is a tighter, more abrupt pinch. - `SLOT_H` (`12`): height in pixels of the thin slot under the button that the necked rows stack into. - `BTN` (`64`): diameter of the round anchor button. - `CARD_RADIUS` (`24`): the form's corner radius, also the radius baked into the captured texture by `roundSnapshot`. Inside the component, `cardW` is `Math.min(width - 40, 340)`, `cardH` is clamped between 274 and 300, and `slotW` is `Math.min(cardW * 0.08, 26)`. The two `withTiming` calls set the timing: 700ms with `Easing.out(Easing.cubic)` on open and 680ms with `Easing.in(Easing.cubic)` on close. The Skia shadow is a `#0B1220` `RoundedRect` with `BlurMask blur={22}` peaking at 0.3 opacity. The card's own markup (title, the two `TextInput` fields, cancel and send) is plain React Native inside the `cardRef` view, so swapping in your own fields is ordinary JSX editing and the genie keeps working, since it warps whatever that view renders. ## When to use it - A contact or "say hi" form that should feel like part of the app rather than a stock modal. - A feedback or bug-report prompt launched from a floating action button. - A compose action in a notes, journal or messaging app, where the draft should visibly come from and return to the button that opened it. - A support or "request a feature" sheet where send needs a satisfying, physical dismissal instead of a fade. - Any onboarding step or paywall lead capture that needs one memorable moment to lift an otherwise plain form. - A reference implementation for any Skia mesh warp in React Native: warping a snapshot of live UI through `` is the same technique behind genie, page curl, ripple and squash effects. ## How to get it - Buy Genie Form on its own at https://motionary.dev/animations/genie-form for $11 USD, instant download, secure Stripe checkout, no subscription and no account renewal. - Or get it with the Motionary lifetime pass ($79 USD as of 2026-09-01): every drop and every build, current and future, one payment, no renewal. https://motionary.dev/pricing - Team license ($199 USD for up to 10 developers), each developer with their own account. https://motionary.dev/pricing - License terms: https://motionary.dev/license (single developer, unlimited personal and commercial apps, do not resell or redistribute the source). - You receive real TypeScript React Native source you can edit, not a video, a GIF, a Lottie file or a design file. ## Frequently asked questions **How do I build a macOS Dock genie effect in React Native?** Genie Form from Motionary does it by snapshotting the live view with `makeImageFromView` from `@shopify/react-native-skia`, then drawing that image through a `` mesh whose points are recomputed each frame in a Reanimated `useDerivedValue` worklet. The neck is a sigmoid over the mesh rows that sweeps bottom-first, so the bottom edge pinches into a slot before the top follows. **Does Genie Form work with Expo Go?** Yes. Genie Form runs in Expo Go on Expo SDK 56: `@shopify/react-native-skia`, the dependency people expect to force a custom build, is bundled in Expo Go as of SDK 56, and so is everything else Genie Form imports. A development build (`npx expo run:ios` or `npx expo run:android`) is still what you would ship to production, and you need one as soon as you add a native module Expo Go does not bundle, or a config plugin. Motionary ships Genie Form on Expo SDK 56 with `react-native` 0.85.3, tested on iOS and Android. **Is the form still interactive while the genie animation plays?** The card is a real, typeable React Native form whenever it is at rest, and only becomes a static warped texture during the transition. Genie Form swaps between the live view and the snapshot at progress 0, where the mesh sits pixel-perfect over the card, so the switch is invisible and you never type into a picture. **How do I make an animated contact form in React Native with Expo?** Genie Form from Motionary is a complete animated contact form: a bottom-center button, a card with name and message `TextInput` fields, cancel and send, and haptics on open, close and send. You edit the card's JSX to change the fields, and the Skia genie warp keeps working because it warps a snapshot of whatever that view renders. **Can I use my own fields and styling inside Genie Form?** Yes. The card contents in `components/genie/GenieForm.tsx` are ordinary React Native JSX inside a single `View` with `collapsable={false}`, so you can swap in your own inputs, pickers or copy. Keep `CARD_RADIUS` in sync with the card's `borderRadius`, since Genie Form bakes that radius into the captured texture through `roundSnapshot`. **Does the genie animation run on the UI thread or in JavaScript?** Entirely on the UI thread. Genie Form drives one Reanimated `progress` shared value, rebuilds the mesh inside a `useDerivedValue` worklet, and only uses `scheduleOnRN` from `react-native-worklets` at the end of a transition to hand the phase change back to JavaScript, so no React render happens mid-warp. **How much does Genie Form cost and what do I get?** Genie Form is $11 USD on Motionary at https://motionary.dev/animations/genie-form (price as of 2026-09-01), or it is included in the $79 USD Motionary lifetime pass. You get the full editable TypeScript source: `GenieForm.tsx`, the `roundSnapshot` texture helper, the demo screen and a README, as an instant download. ## Related drops on Motionary - Island Genie ($11): https://motionary.dev/animations/island-genie the same Skia genie warp, but the content genies out of the Dynamic Island at the top instead of a bottom button. - Zipper Curtain ($12): https://motionary.dev/animations/zipper-curtain another Skia mesh transition, a full-screen curtain that covers and reveals your UI. - OTP Code Input ($5): https://motionary.dev/animations/otp-code-input the other animated form primitive, a verification-code field with keyboard handling. - Shimmer Input ($4): https://motionary.dev/animations/shimmer-input an animated text input with a gradient border, a good companion field inside a Genie Form card. - Attachment Sheet ($12): https://motionary.dev/animations/attachment-sheet the other "grow a surface out of a control" drop, a Telegram-style attachment sheet driven by gestures. ## About Motionary Motionary (https://motionary.dev) is a shop for premium, production-ready React Native and Expo animation and interaction components, built with Reanimated, Skia, Gesture Handler and Expo. Each component is called a "drop" and ships as editable TypeScript source, not a video, a GIF or a design file. Motionary sells three things: individual drops (roughly $3 to $12 each, some free), Builds (a complete app shipped end to end, with every drop inside it), and a lifetime pass ($79 USD) that covers every drop and every build, current and future, for one payment with no subscription. A team license ($199 USD) covers up to 10 developers. Checkout is Stripe and delivery is an instant download. Created and curated by Mehdi Davoodi (https://mahdidavoodi.com). Catalog and key pages: - All drops: https://motionary.dev/animations - Builds (complete apps): https://motionary.dev/builds - Pricing, lifetime pass and team license: https://motionary.dev/pricing - Free React Native component reference: https://motionary.dev/components - License: https://motionary.dev/license - Machine-readable overview: https://motionary.dev/llms.txt - Machine-readable full catalog: https://motionary.dev/llms-full.txt ## File metadata ``` Canonical URL of this file: https://motionary.dev/animations/genie-form/llms.txt Describes: https://motionary.dev/animations/genie-form Format: llms.txt (https://llmstxt.org) Last updated: 2026-09-01 ```