Skip to content
Native Animation
Set Tracker Session animation preview

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

npx nativeanimation add set-tracker-session
Skia

Set Tracker Session

A workout set tracker with an animated exercise queue and trophy reveal, with configurable exercises, trophy image, palette, 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 workout set tracker with an animated exercise queue and trophy reveal, with configurable exercises, trophy image, palette, and fonts.

Why this one

Workout loggers often feel like forms. This one gives each recorded set a yellow fill, a rolling set number, a small dot burst, and a lift in the performed-rep tile. Finished exercises move into the completed group; the next exercise takes focus. The final session becomes a reward with a trophy arrival, silhouette-masked light, counting metrics, and staggered milestones.

How it works

  1. React stores semantic events only: the active exercise, recorded sets, performed reps, and saved state. A synchronous event ledger keeps rapid imperative calls consistent.
  2. Recording a set starts a 300 ms shared-value fill and a 520 ms burst. Independent pressure springs give every button immediate 0.97-scale feedback.
  3. Each numeric column derives its position from a shared value. Higher columns move only on a carry; fading faces finish on exact integer positions. Separators and unit labels occupy their own layout space.
  4. A completed exercise closes its continuous Skia status ring into a Lucide check. Its row moves above the new active exercise in the same render; the next exercise name and metrics mount together in a single commit. The complete workout fades out before the celebration fades in, with no travel or text overlap; autoplay resets use the same sequential fade.
  5. One celebration clock drives the trophy arrival, a spring-followed 12° tilt, alpha-masked shine, metric count-up, and 90 ms milestone cascade. The trophy stays inside a clipped slot with at least 16 pt before the heading. Static Lucide glyphs use inline SVG on web to avoid a WebGL context per icon. Reduced motion uses 150–200 ms fades and removes travel, spinning, particles, and shine.
  6. The demo owns autoplay through the public ref: two sets, the next exercise, an accelerated queue, the reward, and reset. A user action stops autoplay.

Install

Copy this folder without demo/ (keep both SessionIcon platform files), then install compatible dependencies:

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

Use the Reanimated/worklets setup supplied by your Expo SDK. On web, load CanvasKit before importing this component with Skia's supported web loader. In this catalog, the host registers Skia pieces using its deferred Skia loader. Icons: Lucide (ISC); the copied paths live in icons.ts.

Usage

import { useRef } from 'react';
import { SetTrackerSession, type SetTrackerSessionRef } from './set-tracker-session';

const ref = useRef<SetTrackerSessionRef>(null);

<SetTrackerSession
  ref={ref}
  exercises={[
    { id: 'press', name: 'Incline Press', sets: 4, reps: 10, weight: 70, level: 'Advanced' },
    { id: 'row', name: 'Cable Row', sets: 3, reps: 12, weight: 55 },
  ]}
  summary={{ minutes: 58, exercises: 2, totalExercises: 2, calories: 684, reps: 39 }}
  trophy={require('./assets/trophy.png')}
  milestones={[{ id: 'record', title: 'New personal record', icon: 'trophy' }]}
  onSetComplete={(exercise, set, reps) => recordSet(exercise.id, set, reps)}
  onSessionComplete={(summary) => recordSession(summary)}
  onSave={(summary) => persistSession(summary)}
/>

The recordSet, recordSession, and persistSession functions belong to your app. Saving and sharing are callbacks; this component does not persist data or open a share sheet itself.

Customize the full palette, copy, units, and timing:

<SetTrackerSession
  exercises={plan}
  summary={summary}
  fontFamily="YourInterfaceFont"
  monoFontFamily="YourCounterFont"
  duration={300}
  celebrationDuration={850}
  trophySize={150}
  labels={{ weightUnit: 'kg', complete: 'Record set', amazing: 'Session complete' }}
  palette={{
    background: '#F5F7FA', surface: '#FFFFFF', border: '#DDE3EC',
    text: '#142132', muted: '#53657A', accent: '#FFE15A', accentInk: '#171A12',
    blue: '#3D7BFF', success: '#16794E', successSurface: '#E6F4EC',
  }}
/>

Provide trophy artwork at least three times its rendered size; the demo's cropped 401×485 trophy renders at at most 160 pt with contain fitting.

Props

PropTypeDefaultPurpose
exercisesreadonly SessionExercise[]RequiredStable IDs, names, sets, reps, weight, optional level.
summarySessionSummaryRequiredCaller-owned final minutes, exercise count, calories, and reps.
milestonesreadonly SessionMilestone[][]Reward titles, detail, and bundled Lucide glyph.
trophyDataSourceParamLucide fallbackLocal or remote Skia image source.
initialExerciseIndexnumber0Earlier exercises begin completed.
initialCompletedSetsnumber0Recorded sets for the first active exercise.
initialRepsnumberExercise targetInitial performed repetitions.
palettePartial<SessionPalette>Night blue/yellowAll surface, text, border, light, shadow, and status colors.
labelsPartial<SessionLabels>American EnglishAll structural copy, units, and announcements.
fontFamilystringSystemInterface typeface.
titleFontFamilystringfontFamilyExercise and completion headings.
monoFontFamilystringfontFamilyRolling counters.
headerReactNodeNoneHost-owned brand or header.
durationnumber300Set fill duration in ms.
celebrationDurationnumber850Reward timeline duration in ms.
trophySizenumber160Trophy size; hard maximum 180 pt.
queueWindownumber5Number of visible rows including the latest completed row.
formatNumber(number) => stringStringAlternate formatting; custom formats use static accessible text.
onSetComplete(exercise, completedSets, reps) => voidNoneOne callback for each recorded set.
onSessionComplete(summary) => voidNoneOnce per finished session.
onSave, onShare(summary) => voidNoneHost persistence and sharing actions.
onInteraction() => voidNoneStops demo autoplay or informs the host.
styleStyleProp<ViewStyle>NoneRoot override.
testIDstringset-tracker-sessionPrefix for stable control identifiers.

The ref exposes completeSet(), finishSession(), and reset(). finishSession() opens the supplied summary without synthesizing individual set callbacks. Finish completes the active exercise; Start switches to a pending exercise while preserving previously recorded sets. reset() restores the initial configuration.

Compatibility

The root uses flex: 1, measures its own container with onLayout, limits content width to 420 pt, and scrolls when needed. It reserves 54 pt at the top and keeps the header left-aligned for the catalog close control. The host owns safe-area insets; wrap this component in your app's safe-area container. Small slots and landscape rely on scrolling rather than shrinking touch targets. Supply stable exercise IDs and positive integer set counts. Change a React key to replace a workout plan, or update props and call reset().

Built for Expo SDK 58, React Native 0.88.0, Reanimated 4.7.1, and Skia 2.14.0. These native modules are included in this project's Expo Go setup.

Accessibility

All actions have button roles, spoken labels, and at least 48 pt touch dimensions. Rep controls, queue actions, saving, and sharing have stable test IDs. Screen readers receive semantic announcements for recorded sets, exercise changes, session completion, and saving. Rolling numbers have a single readable numeric label. Every interaction has a direct tap action; there is no gesture-only operation. Reduced motion replaces movement with fades and omits particles and light sweeps.