Skip to content
Native Animation
Savings Milestone Bar animation preview

This preview uses fictional demo content. See the props below for the customization options available in this component.

npx nativeanimation add savings-milestone-bar
Black friday

Savings Milestone Bar

A cart reward bar with rolling amounts and tier celebrations, with configurable milestones, labels, colors, and fonts.

$9 for this animation

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.

About this animation

A cart reward bar with rolling amounts and tier celebrations, with configurable milestones, labels, colors, and fonts.

Why this one

The next reward is expressed in money. Each earned tier gets a check, pulse, eight sparks, a wave, and a peeling sticker that settles into the earned-rewards row. Removing items fades the reward back to locked. Re-earning it celebrates again.

How it works

  1. Pass the cart subtotal before discounts. Positive, unique milestones are sorted by amount.
  2. One UI-thread fill value springs toward total / lastMilestone with damping 18, stiffness 160, and explicit mass 1. The rendered fill stays inside the track during overshoot.
  3. Each node watches that progress and its eligibility. A threshold can celebrate only while the actual subtotal qualifies, so spring overshoot cannot award an unearned tier.
  4. Crossings enter a queue with at least 260 ms between celebrations. Stale celebrations are discarded if an item is removed. Mounting with an already-qualified total displays existing rewards without announcements or callbacks.
  5. Each sticker springs up, stays for 1,600 ms, then shrinks toward its pill. A clipped reflection runs inside the fill every 2,400 ms. Completing the last tier changes the fill to solid accent.
  6. Currency characters animate only when they change and finish at their exact baseline. React updates occur on cart changes and reward events, never every animation frame.

Install

npx expo install expo-blur expo-image expo-linear-gradient react-native-reanimated react-native-worklets

Use the Reanimated/Worklets configuration recommended for your Expo SDK. No images, fonts, icons package, or network requests are required. Icons: Lucide (ISC); the included paths are rendered as inline SVG by expo-image.

Usage

import { SavingsMilestoneBar } from './savings-milestone-bar';

<SavingsMilestoneBar total={32} />

Place it above your checkout button. The host owns screen positioning, safe-area padding, and cart calculations. The card fills its container width and has a 112-point minimum height. Content may grow with larger text or custom copy.

import { useRef } from 'react';
import { View } from 'react-native';
import { BlurTargetView } from 'expo-blur';
import { SavingsMilestoneBar } from './savings-milestone-bar';

function CartRewards({ subtotal }: { subtotal: number }) {
  const background = useRef<View>(null);
  return (
    <View>
      <BlurTargetView ref={background} style={{ position: 'absolute', inset: 0 }}>
        {/* Your background artwork. */}
      </BlurTargetView>
      <SavingsMilestoneBar
        total={subtotal}
        blurTarget={background}
        milestones={[
          { amount: 50, label: 'Free shipping', icon: 'truck' },
          { amount: 100, label: 'Bonus gift', icon: 'gift' },
          { amount: 150, label: '15% off', icon: 'percent' },
        ]}
        fontFamily="Geist_500Medium"
        monoFontFamily="GeistMono_400Regular"
        labels={{ add: 'Add {amount} for {reward}' }}
        springDamping={22}
        shimmerDuration={2800}
        onMilestone={(reward) => saveEarnedReward(reward.amount)}
      />
    </View>
  );
}

saveEarnedReward represents your own application callback. Load custom fonts in the host; omitting the font props uses the system font.

For Android glass, the supplied BlurTargetView must cover the bar's bounds. Android 12+ uses the efficient native blur; older Android versions use a translucent fallback. Without blurTarget, Android also uses the fallback. For a light theme, set blurTint="systemThinMaterialLight" and override the complete palette, including text, muted, track, onAccent, pill, and shadow.

Props

PropTypeDefaultPurpose
totalnumberRequiredPre-discount cart subtotal; invalid/negative values become zero.
milestonesreadonly SavingsMilestone[]$50 / $100 / $150Amount, label, and truck, gift, or percent icon. Invalid and duplicate amounts are ignored.
currencystringUSDISO currency used with the US locale. Invalid currency codes fall back to USD.
formatAmount(amount: number) => stringUS currency formatterAll currency formatting.
onMilestone(reward, sortedIndex) => void—Called when a reward celebration starts. Fires again after relocking and re-earning.
colorsPartial<SavingsMilestoneColors>Dark volt paletteSurface, border, text, muted, track, accent, success, onAccent, pill, shine, shadow.
labelsPartial<SavingsMilestoneLabels>English copyadd, unlocked, allUnlocked, progress, empty. Messages interpolate {amount} and {reward}.
fontFamily / monoFontFamilystringSystemHost-loaded interface and amount fonts.
heightnumber112Minimum card height; not a fixed clipping height.
nodeSizenumber28Node diameter, minimum 20.
trackHeightnumber10Track thickness, minimum 2.
contentScalenumber1Spacing and text scale, minimum 0.7.
springDamping / springStiffnessnumber18 / 160Interruptible fill physics, with mass 1.
shimmerDurationnumber2400Reflection period, minimum 600 ms.
stickerDurationnumber1600Sticker hold after entry, in milliseconds.
blurTargetRefObject<View | null>—Android background target.
blurTintLight/dark material tintsystemThinMaterialDarkGlass appearance.
styleStyleProp<ViewStyle>—Container style.
testIDstringsavings-milestone-barRoot ID; tiers append -tier-N.

Use three well-spaced tiers for the default compact design. For many tiers, small gaps, or long labels, provide a larger container or smaller content scale. The demo provides three extras, quantity controls with 48-point targets, light/dark palettes, and a repeating autoplay. Any cart interaction pauses autoplay so manual changes remain under the user's control. Checkout only confirms this demo state; it does not process payments.

Accessibility

The whole reward display has the progressbar role and exposes its minimum, maximum, current amount, next reward, and earned rewards as accessible text. Every upward crossing announces the configurable unlock message. Decorative rolling characters and SVG icons are hidden from assistive technology. With reduced motion, the fill, checks, pills, and rolling copy fade in 200 ms; there are no pulses, waves, flying stickers, sparks, or repeating reflections. The component has no controls and does not take focus away from the cart.

Compatibility

Targets Expo SDK 58, React Native 0.88.0, Reanimated 4.7.1, and Worklets 0.13.0. Uses Expo Go modules with no custom native dependency. Supports iOS, Android, and web. Physical-device frame rate, large accessibility text, and RTL layout require host-app validation.