Skip to content
Native Animation
Shutter Deal Reveal animation preview

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

npx nativeanimation add shutter-deal-reveal
SkiaBlack friday

Shutter Deal Reveal

A scheduled offer reveal with a rolling shutter and countdown, with configurable offer content, 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 scheduled offer reveal with a rolling shutter and countdown, with configurable offer content, labels, colors, and fonts.

Why this one

The countdown belongs to the shutter: every slat clips its own slice of the lettering and folds toward the growing roller. Opening feels like a store coming to life, with a light sweep and twelve floating dust motes.

How it works

  1. The container measures its own bounds with onLayout; slats use fractions of that box.
  2. A single open shared value drives travel, perspective folding, roller growth, the light beam, and particles. A separate tick value adds the countdown vibration.
  3. The wall-clock countdown updates only its displayed seconds. The final ten seconds use the urgency color.
  4. At zero, a 120 ms downward anticipation precedes a 900 ms opening. The content settles from 0.94 scale with an explicit mass-one spring.
  5. After the deadline, the handle accepts upward drags or a tap. Release projects velocity to the nearest endpoint and settles with a spring. close() plays two small floor rebounds.
  6. Children become interactive and accessible only when opening finishes. Reduced motion replaces movement with a 200 ms crossfade.

Install

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

Copy this folder except demo/. Wrap your app in GestureHandlerRootView. On web, initialize CanvasKit before importing the component (for example, the host's LoadSkiaWeb/lazy-loading setup) and serve canvaskit.wasm. The demo additionally uses expo-image for a bundled transparent camera photo, passed to the offer as a local source prop. The photo sits above a volt pedestal and a faded reflection; the shutter supplies the opening zoom and light sweep. The 720 px asset supports the capped 240 pt display width at 3× resolution.

Usage

import { useRef } from 'react';
import { Text, View } from 'react-native';
import { ShutterDealReveal, type ShutterDealRevealRef } from './shutter-deal-reveal';

function Offer({ launchAt }: { launchAt: number }) {
  const shutter = useRef<ShutterDealRevealRef>(null);
  return (
    <View style={{ width: 300, height: 400 }}>
      <ShutterDealReveal ref={shutter} opensAt={launchAt}>
        <Text>Your offer</Text>
      </ShutterDealReveal>
    </View>
  );
}

Custom material and copy:

<ShutterDealReveal
  opensAt={launchAt}
  slatCount={18}
  duration={760}
  timerSizeRatio={0.1}
  colors={{ label: '#6EE7FF', light: '#6EE7FF', urgency: '#FF916E' }}
  labels={{ heading: 'AVAILABLE IN', pull: 'Lift to reveal', open: 'Open offer', opened: 'Your offer is ready' }}
  onOpen={() => setOfferVisible(true)}
>
  <OfferCard />
</ShutterDealReveal>

Keep opensAt stable across renders. Changing it schedules another automatic opening; closing alone does not restart or retrigger the expired countdown. This is presentation, not server-side sale authorization. The host controls eligibility and price validation.

Props

PropTypeDefaultPurpose
opensAtDate | numberRequiredDeadline, as a Date or Unix milliseconds.
onOpen() => void—Fires at full opening, once per opening.
allowManualOpenbooleantrueHandle tap and drag after the deadline.
slatCountnumber14Individually folding slats, clamped to 4–32.
colorsPartial<ShutterDealRevealColors>Dark voltMetal, highlight, shadow, seam, label, light, urgency, handle, background.
labels{ heading?, pull?, open?, opened? }OPENS IN / Pull up / Open deal / The deal is now openVisible copy and accessible announcements.
childrenReactNode—Offer content; no embedded demo branding.
fontFamilystringGeist_500MediumHost-loaded interface font.
monoFontFamilystringGeistMono_400RegularHost-loaded countdown font.
timerSizeRationumber0.105Countdown size relative to width, clamped to 12–64 pt.
durationnumber900Opening travel in milliseconds.
styleStyleProp<ViewStyle>—Container layout; root has flex 1.
testIDstringshutter-deal-revealRoot identifier; handle adds -handle.

Ref: open() bypasses the schedule; close() lowers the shutter without restarting the timer. Colors can be overridden independently or supplied as a complete light palette. Supply contrasting seam, handle, label, and background colors together when changing material brightness.

Compatibility

Targets Expo SDK 58, React Native 0.88.0, Reanimated 4.7.1, Worklets 0.13.0, Gesture Handler 3.3, and Skia 2.14.0. Uses Expo Go's installed modules; no new native dependency is added. Supports iOS, Android, and web.

The host owns safe areas and font loading. The component fits its assigned box and applies no screen insets. Children are responsible for their own responsive content and font scaling. The included demo switches to a compact header for small containers and a side-by-side layout in landscape.

Accessibility

The countdown exposes the timer role without announcing every tick. The handle has a 120 × 58 pt button area, a configurable accessible name, an activation action, and a tap alternative to the drag. Opening announces labels.opened; concealed content is excluded from accessibility until fully revealed. Reduced motion removes travel, rotation, dust, vibration, scale, and rebounds in favor of 200 ms opacity changes.