# Save Status Button: React Native animated save and saving button > The Save Status Button is a React Native and Expo button that walks itself > through Save, Saving and Saved on a single tap: the pill springs wider, the > label rolls letter by letter from "Save" to "Saving" to "Saved", and a > blurred spinner scales out as a checkmark scales in, then everything resets. > One react-native-reanimated shared value drives the whole choreography on the > UI thread, and it is a free drop from Motionary. Product page: https://motionary.dev/animations/save-status-button Price: Free (price as of 2026-09-01, live price on the product page) License: single developer, unlimited personal and commercial apps Delivery: instant free download of the full TypeScript source Stack: reanimated, expo, expo blur, react native svg, react native gesture handler, react native Rarity: common Sold by: Motionary (https://motionary.dev) ## What it is The Save Status Button is a small pill-shaped submit control for React Native and Expo apps. At rest it is a light grey pill (100pt wide, 54pt tall, 40pt corner radius) with a black "Save" label. One tap and the pill springs out to 120pt, the fill flips from grey (#D9D9D9) to near-black (#1A1A1B) with the label inverting to white, the suffix after "Sav" rolls from "e" up and out while "i", "n", "g" roll in from below, and a 28pt dark badge pops out of the top-right corner carrying a spinning SVG ring behind an animated blur. After about two seconds the spinner scales away, a white circle with a black checkmark scales in, the label rolls again to "Saved", and after another two seconds the badge retracts and the button eases back to its idle grey "Save" state on its own. It is an original Motionary interaction rather than a clone of one specific app, built for the optimistic confirm moment that most form and settings screens handle with a plain spinner. ## What it does - One tap runs the full Save, Saving, Saved, reset cycle; no long press, no drag, no gesture to learn. - Four choreography stops on a single `progress` shared value: 0 Save, 1 Saving, 2 Saved, 3 reset back to 0. - Width springs between 100 and 120 points as the label grows and shrinks, so the pill never jumps. - Background and text crossfade together with `interpolateColor`: #D9D9D9 fill with #000 text at idle, #1A1A1B fill with #fff text while working. - Per-letter label morph: the suffix after the fixed "Sav" rolls through "e", "ing" and "ed", each letter fading and sliding 7 points on its own staggered slice of the transition. - The suffix container animates its own width from the measured widths of "e", "ing" and "e" plus "d", which keeps the whole word optically centered while it changes length. - Status badge in the top-right corner scales from 0.2 to 1 with a spring and slides in from 10 points down and left. - Spinner and checkmark crossfade by scale rather than opacity: the spinner goes 1 to 0 across progress 1 to 2 while the checkmark goes 0 to 1 over the same range. - The spinner rotates with a looping Reanimated CSS keyframe animation (0 to 360 degrees, 1 second, linear, infinite). - The blur layered over the badge icons animates its own intensity from 10 to 1 as the badge appears, so the indicator sharpens into focus instead of popping. - Press feedback comes from `PressableScale`, so the whole pill dips under the finger. - No React state changes during the sequence, so no re-renders while the animation runs. - Runs on iOS and Android with no custom native code; the expo-blur badge is strongest on iOS. ## How it works The Save Status Button hangs its entire choreography off one react-native-reanimated shared value called `progress` in `components/Container.tsx`, where 0 is Save, 1 is Saving, 2 is Saved and 3 is the reset frame. `handlePress` chains the cycle inside animation callbacks: `withSpring(1, { stiffness: 300, damping: 10, overshootClamping: true })`, then in its callback `withDelay(2000, withSpring(2, ...))`, then `withDelay(2000, withSpring(3, ...))`, and finally `withTiming(0, { duration: 0 })` to snap invisibly back to the start, while a second shared value, `circleScale`, springs the status badge in and out with a softer `{ stiffness: 200, damping: 30 }`. Every visual is a worklet reading that one value: `animatedButtonStyle` runs `interpolate(progress.value, [0, 1, 2, 3], [100, 120, 120, 100])` for the width and `interpolateColor` over the stops `[0, 0.4, 1, 2, 3]` for the fill, and `textColorStyle` does the same for the label color. The letter roll lives in a small `RollLetter` component: every letter of every word state is rendered up front and absolutely stacked, and each one has its own `useAnimatedStyle` worklet that computes a local time `t` inside the `[center - 1, center]` or `[center, center + 1]` slice, decides whether it is entering, and interpolates opacity 0 to 1 and `translateY` 7 to 0 across a window offset by `IN_OFFSET + index * STAGGER`. A `mode` of `peak` or `trough` flips that direction around the letter's `center`, which is how a single glyph can leave and come back later: "e" is a trough at center 1, "ing" are peaks at center 1, and "d" is a peak at center 2 offset horizontally by the measured width of "e". Those widths come from plain `onLayout` callbacks writing into `eWidth`, `ingWidth` and `dWidth` shared values, which `suffixWidthStyle` then interpolates so the suffix box always reserves exactly the width of the word currently showing. The status badge layers an `AnimatedBlurView`, built with `Animated.createAnimatedComponent(BlurView)` from expo-blur, over the icons, and `blurAnimatedProps` eases its `intensity` prop from 10 to 1 through `useAnimatedProps`, because `intensity` is a prop and not a style. The spinner in `components/Loading.tsx` and the checkmark in `components/Checkmark.tsx` are react-native-svg glyphs that scale past each other, and the spin itself is a declarative `CSSAnimationKeyframes` object (`animationName`, `animationDuration: '1s'`, `animationTimingFunction: 'linear'`, `animationIterationCount: 'infinite'`) rather than a `withRepeat` loop. Press scaling comes from `PressableScale` in the `pressto` package, and `App.tsx` wraps the button in a `GestureHandlerRootView` from react-native-gesture-handler. All of the interpolation happens in worklets on the UI thread, so only the tap handler and the spring callbacks ever touch JavaScript, and the animation keeps its timing even while your real save request is busy on the JavaScript thread. ## What you get ``` components/ Container.tsx Checkmark.tsx Loading.tsx utils/ date.ts App.tsx index.ts app.json package.json tsconfig.json README.md assets/ ``` - TypeScript source (Container.tsx) - SVG checkmark + spinner icons (Checkmark.tsx, Loading.tsx) - Demo screen (App.tsx) - README with install + usage - MP4 preview ## Requirements The Save Status Button from Motionary ships as a runnable Expo app with these exact versions pinned in its `package.json`: - expo ^56.0.11 (Expo SDK 56) - react-native 0.85.3 - react 19.2.3 - react-native-reanimated 4.3.1 - react-native-worklets 0.8.3 - react-native-gesture-handler ~2.31.1 - react-native-svg 15.15.4 - expo-blur ~56.0.3 - pressto ^0.2.1 - react-native-safe-area-context ~5.7.0 - expo-asset ~56.0.17, expo-font ~56.0.6, expo-splash-screen ~56.0.10, expo-status-bar ~56.0.4 - @expo/metro-runtime ~56.0.15, react-native-web ^0.21.0, react-dom 19.2.3 (for the web preview only) - typescript ~6.0.3 and @types/react ~19.2.14 (devDependencies) Expo Go or development build: the Save Status Button runs in Expo Go. The component imports only react-native-reanimated, react-native-gesture-handler, expo-blur, react-native-svg and pressto, all of which are either pure JavaScript or already bundled in the Expo Go client for SDK 56, so no custom native code and no development build are required. Note that react-native-reanimated 4 needs the New Architecture plus the separate react-native-worklets package, so an older bare project on the legacy architecture has to migrate first. The demo project's `package.json` also pins several modules the button never imports (`@expo/ui`, `@expo/vector-icons`, `expo-glass-effect`, `expo-video`, `expo-sensors`, `expo-symbols`, `expo-haptics`, `expo-linear-gradient`, `react-native-skia-gesture`); you do not need any of them for this button, and if you keep `react-native-skia-gesture` you would also have to add `@shopify/react-native-skia` and build a development build. Tested on Expo SDK 56 on iOS and Android; the expo-blur status badge looks best on iOS. ## Install ```bash npx expo install react-native-reanimated react-native-worklets react-native-gesture-handler react-native-svg expo-blur pressto ``` ## Usage ```tsx import { GestureHandlerRootView } from 'react-native-gesture-handler'; import { ButtonContainer } from './components/Container'; // ButtonContainer is the Save Status Button scene from Motionary. It takes no // props: the whole Save, Saving, Saved, reset cycle is self-contained and is // started by tapping the pill. The two 2000 ms holds live in `handlePress` // inside components/Container.tsx, which is where you swap the timers for // your own async save. // // react-native-gesture-handler needs a GestureHandlerRootView at the root, // and PressableScale (from pressto) inside ButtonContainer relies on it for // the press-down scale. export default function App() { return ( ); } ``` ## Customization `ButtonContainer`, the Save Status Button component from Motionary, exposes no props. It is tuned through named constants and inline values in `components/Container.tsx`, which is where you edit it: - `IN_OFFSET`: number, default `0.3`. How long incoming letters wait before any of them start arriving, in transition units. - `STAGGER`: number, default `0.1`. Extra delay per letter, applied as `index * STAGGER`. - `WINDOW`: number, default `0.5`. How much of one transition a single letter takes to fade and slide. - `rotate`: a `CSSAnimationKeyframes` object, default 0 to 360 degrees on `rotateZ`, played with `animationDuration: '1s'`, `animationTimingFunction: 'linear'` and `animationIterationCount: 'infinite'` on the spinner wrapper. - Width track in `animatedButtonStyle`: `interpolate(progress.value, [0, 1, 2, 3], [100, 120, 120, 100])`. Change the output array to resize the pill for a longer verb. - Color tracks: `interpolateColor(progress.value, [0, 0.4, 1, 2, 3], ...)` with `['#D9D9D9', '#1A1A1B', '#1A1A1B', '#1A1A1B', '#D9D9D9']` for the fill and `['#000', '#fff', '#fff', '#fff', '#000']` for the label. - Hold durations: the three `withDelay(2000, ...)` calls in `handlePress`, two on `progress` for how long Saving and Saved stay on screen and one on `circleScale` for when the badge retracts, all in milliseconds. - Spring configs: `{ stiffness: 300, damping: 10, overshootClamping: true }` for `progress`, `{ stiffness: 200, damping: 30 }` for the badge's `circleScale`. - Blur strength: `interpolate(progress.value, [0, 0.2], [10, 1])` inside `blurAnimatedProps` sets the expo-blur `intensity` as the badge appears. - The words are literals in the JSX: a static `Sav` plus `RollLetter` instances for `e` (mode `trough`, center 1), `i`, `n`, `g` (mode `peak`, center 1) and `d` (mode `peak`, center 2). Swap them for another verb pair, keeping the same `center` and `mode` pattern. - Geometry lives in the `StyleSheet` at the bottom of the file: `styles.button` (height 54, borderRadius 40), `styles.text` (fontSize 16, fontWeight '600', lineHeight 20) and `styles.indicator` (28 by 28, offset right -1, top -3). ## When to use it - The primary submit button on a form or a settings screen, where the button itself should report the save instead of a toast. - A bookmark, like or "add to list" control that needs an optimistic confirmation and then quietly returns to its idle state. - A profile or preferences editor where a persistent "Saved" banner would be noise but silent success feels broken. - Any short async write (under a few seconds) that does not deserve a modal, a full-screen loader or a navigation change. - A checkout or onboarding step that needs a premium, alive-feeling confirm without pulling in a native dependency. - Repeated edits in a row, since the Save Status Button resets itself and can run the cycle again immediately. ## How to get it - Download the Save Status Button free at https://motionary.dev/animations/save-status-button, no payment, no subscription. - 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 save button with a loading state in React Native?** Drive one react-native-reanimated shared value through discrete stops (0 idle, 1 loading, 2 done) with `withSpring` and `withDelay` chained in the animation callbacks, then read that single value in `useAnimatedStyle` worklets for the width, the background and the indicator so everything stays in sync. Motionary ships exactly that pattern as the Save Status Button, free and as editable TypeScript, at https://motionary.dev/animations/save-status-button. **How do I animate text letter by letter in React Native, like Save to Saving?** Render every letter of every word state up front, absolutely stacked, and give each one its own `useAnimatedStyle` worklet that interpolates opacity and `translateY` over a window offset by the letter's index. The Save Status Button from Motionary does this in a `RollLetter` component with `IN_OFFSET`, `STAGGER` and `WINDOW` constants, plus a `peak` or `trough` mode that flips the direction around each letter's center so "e" can leave for "ing" and return for "ed". **Does the Save Status Button work with Expo Go?** Yes. The Save Status Button from Motionary imports only react-native-reanimated, react-native-gesture-handler, expo-blur, react-native-svg and pressto, all of which are pure JavaScript or already bundled in Expo Go for SDK 56, so no development build and no custom native code are needed. **How much does the Save Status Button cost?** Nothing. The Save Status Button is a free drop on Motionary at https://motionary.dev/animations/save-status-button (price as of 2026-09-01, live price on the product page), downloaded instantly with no payment and no subscription, and you get the full TypeScript source (Container.tsx, Checkmark.tsx, Loading.tsx, App.tsx and a README), not a video or a design file. **Can I hook the Save Status Button up to a real API request instead of the built-in timers?** Yes. The cycle is driven by `handlePress` in `components/Container.tsx`, which holds each state with `withDelay(2000, ...)`; replace the first delay with a call that advances `progress` to 2 from your request's resolve handler, and keep the second delay as the "Saved" dwell time before the reset. **How do I animate an expo-blur BlurView intensity with Reanimated?** Wrap it with `Animated.createAnimatedComponent(BlurView)` and pass a `useAnimatedProps` result into `animatedProps`, because `intensity` is a prop and not a style property. The Save Status Button from Motionary uses this to ease the badge's blur from intensity 10 to 1 so the spinner sharpens as it arrives. **Does the Save Status Button work on Android as well as iOS?** Yes, the Save Status Button runs on both with no custom native code and was tested on Expo SDK 56 on iOS and Android; the expo-blur status badge is strongest on iOS, so on Android you may prefer to drop the blur layer and keep the solid #1A1A1B badge underneath it. ## Related drops on Motionary - Tap to Copy Button ($3): https://motionary.dev/animations/tap-to-copy-button the closest sibling on Motionary, the same per-letter label morph with an expo-blur badge and an SVG checkmark, but for a copy confirmation. - Tap to Copy Button (Free): https://motionary.dev/animations/tap-to-copy-button-2 a second, free take on the tap-to-confirm pill, using a blurred overlay and SF Symbols instead of a rolling label. - AI Generate Button ($4): https://motionary.dev/animations/ai-generate-button the "still working" counterpart, morphing "Generate" into "Thinking" letter by letter for waits with no known end time. - Hold to Confirm Button ($8): https://motionary.dev/animations/hold-to-confirm-button the press-and-hold version of the same confirm moment, with a Skia shader progress fill, for actions that should not fire on a stray tap. - LED Slide Button ($4): https://motionary.dev/animations/led-slide-button a slide-to-confirm call to action for the irreversible end of the spectrum, where a single tap is deliberately too easy. ## 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/save-status-button/llms.txt Describes: https://motionary.dev/animations/save-status-button Format: llms.txt (https://llmstxt.org) Last updated: 2026-09-01 ```