Skip to content
Native Animation
Prism States animation preview

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

npx nativeanimation add lottie-prism-states
SkiaLoader

Prism States

Animated operation scenes and action icons with configurable appearance theme, size, and transition duration.

$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

Animated operation scenes and action icons with configurable appearance theme, size, and transition duration.

How it works

  1. scene selects a local JSON; state selects its exact marker. Scene-specific types reject unrelated states.
  2. A single mutable shared frame drives Skottie. Loops run with useFrameCallback; once markers play at the authored frame rate. Upload and download map real progress into their linear marker.
  3. A state change cancels the old animation and jumps into the destination marker before interpolating. The outgoing pose stays frozen during a 140 ms fade. Rapid interruptions flatten the visible blend into a bounded set of numeric poses.
  4. Thirty cached Skottie instances, created lazily once per scene/palette, remain owned for the module lifetime. No animation or picture is disposed during React cleanup.
  5. Reduced motion shows a representative static pose. Finite markers complete immediately; loaders stop moving. Accessibility percentages update at most four times per second, independently of rendering.

The component does not infer network success, start requests, or change your application state. Your operation remains the source of truth.

Install

Copy this entire folder, including assets/, into your project. No sibling animation folder or shared theme is required.

npx expo install @shopify/react-native-skia react-native-reanimated react-native-worklets

On web, load CanvasKit before importing this module. Use Skia's LoadSkiaWeb with a locally hosted canvaskit.wasm, then dynamically import the component. See Skia web setup. Native apps do not need this step. The demo additionally uses expo-linear-gradient and accepts fonts already loaded by its host.

Usage

import { PrismStateScene, PrismIcon, PrismLoader } from './lottie-prism-states';

<PrismStateScene
  scene="download"
  state="progress"
  progress={bytesReceived / totalBytes}
  accessibilityLabel="Download report"
/>

<PrismStateScene
  scene="save"
  state={isSaving ? 'saving' : 'saved'}
  theme="dark"
  size={180}
  showText
  texts={{ saving: 'Saving your draft', saved: 'Draft saved' }}
  onAnimationEnd={state => {
    if (state === 'saved') acknowledgeSavedDraft();
  }}
/>

<PrismIcon icon="copy" trigger={copyCount} accessibilityLabel="Copied link" />
<PrismLoader accessibilityLabel="Loading workspace" size={40} />

An icon is artwork, not a button. Wrap it in your own accessible Pressable with an effective target of at least 48 points:

const iconRef = useRef<PrismIconHandle>(null);

<Pressable
  accessibilityRole="button"
  accessibilityLabel="Share document"
  style={{ width: 48, height: 48 }}
  onPress={() => {
    shareDocument();
    iconRef.current?.play();
  }}
>
  <PrismIcon ref={iconRef} icon="share" size={40} />
</Pressable>;

PrismIconHandle is exported from the entry point. play plays on mount when true, and on each false-to-true change. A supplied trigger plays initially and whenever its string/number changes. ref.play() always restarts the gesture. Omit all three for the resting pose. Gestures play the authored intro marker and hold its final pose; they do not scrub through unrelated frames to reach the separate poster marker.

Scene states

SceneExact states (marker names)Behavior
uploadidle, progress, success, errorIdle loop, linear progress, once results
downloadidle, progress, paused, success, error, cancelledIdle/paused loops, linear progress, once results
syncsyncing, synced, offline, reconnecting, errorSyncing/reconnecting loop; others once
sendidle, sending, sent, errorIdle loop; others once
savedirty, saving, saved, errorDirty loop; others once
requestloading, success, error, warningLoading loop; others once
emptyfirst-use, cleared, caught-upCleared loop; others once
searchsearching, no-results, results, errorSearching loop; others once

progress is accepted only for upload/download, and only affects their progress state. saving and sending are finite authored gestures; keep your real operation state controlled until it finishes. onAnimationEnd reports every completed once marker, including offline, first-use, and sending; it never reports loops or linear progress. Leaving a marker cancels its callback. Theme changes preserve completion status. Reenter a marker to play it again; repeating identical props does not replay it.

Props

PropComponentsDefaultMeaning
scene, stateStateSceneRequiredDiscriminated scene/state pair
progressStateScene upload/download0Number or SharedValue<number>; clamps to 0–1, nonfinite becomes 0
themeAlllightlight or dark; set from the host's color scheme if desired
sizeAllScene 256 / Icon 64 / Loader 48Preferred square size, shrinks to both parent dimensions
styleAll—Container style; bounded dimensions work, safe areas belong to the host
testIDAllprism-state-scene, prism-icon-<icon>, prism-loaderRoot ID; artwork adds -art
accessibilityLabelAllOperation status / icon name / LoadingLocalizable accessible name
transitionDurationAll140Frozen-pose fade duration, milliseconds; 0 for instant changes
textsStateSceneMarker names, hyphens replaced with spacesPartial map of this scene's state descriptions
showText, fontFamilyStateScenefalse, system fontOptional caption; respects text scaling
smoothingDurationStateScene180Linear-progress interpolation, milliseconds
onAnimationEndStateScene / Icon—Completed state / completed gesture callback on JS
iconIconRequiredcopy, bell, trash, like, share, undo
play, trigger, ref.play()Iconfalse, absent, —Imperative or controlled gesture triggers

Nonnegative finite durations are expected. size invalid values fall back to the component's internal preferred size. Custom fonts are optional and must already be loaded; no fontWeight is paired with a custom font.

Layout and demo

Embedding applies no safe-area padding. Put the component in a bounded parent, for example <View style={{width: 300, height: 400}}>. Artwork fits the smaller dimension after measuring any caption. It stays square without stretching vector geometry. Text remains in normal flow and honors system font scaling.

demo/PrismStatesDemo.tsx exports a separate "Vemrilo / Workspace" screen with complete palettes, spring press feedback, scene picker, state controls, irregular simulated transfer progress, six action buttons, and a background loader. Its only scroll container is the outer screen, with the indicator hidden; compact/landscape hosts can scroll to every control. All demo copy and font names are props. The host maps ?autoplay=1 to <PrismStatesDemo autoplay />; URL reading is outside the reusable piece. Autoplay tours every scene and state, and alternates palettes each full cycle.

Assets and reproduction

Only the 30 light-original / dark-original JSON files are shipped: 653,789 bytes (638.47 KiB), minified. These vectors have no bitmap density limit. To sync from the original factory checkout:

node lottie/scripts/prism-rn/sync-assets.mjs
npx prettier --ignore-path src/animations/lottie-prism-states/.prettierignore --write src/animations/lottie-prism-states

The sync script reads lottie/dist/<id>/metadata.json and the two variants, writes only this folder, and regenerates exact ranges and typed marker contracts. The upload JSON predates stateSegments, so its idle/progress behavior is normalized explicitly. The script prints the total byte count.

Web verification uses a temporary Metro entry outside src/, then removes it:

node lottie/scripts/prism-rn/verify-web.mjs http://127.0.0.1:8092

Development verification requires the repository's Playwright installation in lottie/node_modules; it is not a runtime dependency. Reports and screenshots go to lottie/.cache/prism-rn/. No catalog registration is necessary.

Compatibility

Expo SDK 58, React Native 0.88.0, React 19.2.3, Reanimated 4.7.1, Worklets 0.13.0, Skia 2.14.0. No added dependency. Skia is included in Expo Go for this SDK (Expo SDK 58 reference).

Measured checks are recorded in lottie/.cache/prism-rn/report.json after running the command above.

Accessibility

Linear operation states expose determinate progressbar values; the loader exposes a busy, indeterminate progressbar without a numeric value. Other artwork exposes an image with its localized state description, plus a polite result live region. No React state is updated per animation frame: only throttled accessibility percentages, layout, state changes, and fade cleanup use React. Reduced motion disables loops and fades, and completes finite markers immediately. The host supplies action button semantics when using an icon as a control.