Skip to content
Native Animation
Workout Timer Ring animation preview

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

npx nativeanimation add workout-timer-ring
Skia

Workout Timer Ring

An interval timer with rolling countdowns and work-to-rest transitions, with configurable work duration, exercises, work color, 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

An interval timer with rolling countdowns and work-to-rest transitions, with configurable work duration, exercises, work color, and fonts.

Why this one

Ordinary interval timers replace a number and jump between colors. This timer rolls only the changing columns, sweeps the arc back in over 400 ms, blends the phase color through the ring and radial light, and moves the exercise through a soft blur. The last three seconds pulse; the play/pause silhouette morphs from sampled Lucide contours. Finishing brings a full rotation, a two-color ring, and fine particles.

How it works

  1. One useFrameCallback advances elapsed session time on the UI thread. Pausing freezes that time rather than restarting an animation.
  2. Derived values calculate the round, phase, remaining seconds, and normalized arc endpoint. The clock uses elapsed milliseconds, not frame counts.
  3. Countdown columns roll during the final 180 ms of a second, only when their digit changes. Each column has a fixed width and fades digits by distance.
  4. Phase changes drive a 400 ms refill and color blend. Exercise text starts its transition in useLayoutEffect after the new label commits.
  5. Press feedback uses a separate interruptible spring. Completion stops the clock and drives a single celebration.
  6. Screen-reader announcements and consumer callbacks run on JS via scheduleOnRN, only at phase boundaries.

Install

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

Copy this folder without demo/. Configure Reanimated for your Expo application. On web, load CanvasKit before importing this component (for example using Skia's WithSkiaWeb wrapper and a lazy import). This component must not be eagerly evaluated before CanvasKit loads.

Icons: Lucide (ISC), copied from the repository kit. No external images or fonts are required. For the demo, load the repository's Geist, Geist Mono, and Instrument Serif fonts once at app startup.

Usage

import { WorkoutTimerRing } from './workout-timer-ring';

export function Workout() {
  return <WorkoutTimerRing exercises={['Squats', 'Plank taps']} />;
}
import { useRef } from 'react';
import { WorkoutTimerRing, type WorkoutTimerRingRef } from './workout-timer-ring';

export function CustomWorkout() {
  const timer = useRef<WorkoutTimerRingRef>(null);
  return (
    <WorkoutTimerRing
      ref={timer}
      workSeconds={30}
      restSeconds={15}
      rounds={6}
      sessionLabel="Circuit"
      exercises={['Reverse lunges', 'Mountain climbers']}
      workColor="#FB923C"
      restColor="#5EEAD4"
      onPhaseChange={(phase, round) => reportPhase(phase, round)}
    />
  );
}

Replace reportPhase with your callback. The component initially pauses; call timer.current?.play() from an event or effect to begin. reset(round, remaining) starts a fresh session at the chosen work phase. reset() starts at round 1. next() advances one phase; restartPhase() restarts the current phase; pause() freezes; play() resumes. After completion, the main button starts another session.

Props

PropTypeDefaultDescription
labelsPartial<WorkoutTimerRingLabels>American English defaultsround: Round; of: of; work: WORK; rest: REST; finished: FINISHED. Phase names also apply to the duration summary. Omitted keys retain their defaults.
workSecondsnumber20Work duration, clamped to 1–3599 seconds.
restSecondsnumber10Rest duration, clamped to 1–3599 seconds.
roundsnumber8Whole rounds, minimum 1. Set session configuration before starting.
exercisesreadonly string[]['Exercise']Names repeat to fill the session; an empty list falls back to Exercise.
headerReactNode—Optional product or brand header.
sessionLabelstringIntervalsSession name in the round counter.
workColorstring#F97316Work ring, label, and light color.
restColorstring#2DD4BFRest ring, label, and light color.
backgroundColorstring#050A0ABackground and radial light edge.
fontFamilystringsystemUI text family.
monoFontFamilystringsystemTabular countdown family.
serifFontFamilystringsystemExercise family.
onPhaseChange(phase, round) => void—Work/rest/complete callback with one-based round.
styleStyleProp<ViewStyle>—Root container overrides. Provide a bounded height.
testIDstring—Root test identifier.
durationnumber400Exercise phase color transition duration in milliseconds; workSeconds and restSeconds are separate. Spring physics and reduced-motion timing are unchanged.
workSecondsnumber20Work phase duration in seconds.
restSecondsnumber10Rest phase duration in seconds.
roundsnumber8Number of rounds.
exercisesreadonly string[]DEFAULT_EXERCISESExercise names, repeated when fewer than rounds.
headerReactNodeundefinedHeader content, such as a brand tile.
sessionLabelstring'Intervals'Session label.
workColorstring'#F97316'Work color.
restColorstring'#2DD4BF'Rest color.
backgroundColorstring'#050A0A'Background color.
fontFamilystringundefinedUI font family.
monoFontFamilystringundefinedCountdown font family.
serifFontFamilystringundefinedExercise font family.
onPhaseChange( phase: 'work' | 'rest' | 'complete', round: number, ) => voidundefinedCallback on phase changes, including completion.
styleStyleProp<ViewStyle>undefinedContainer style.
testIDstringundefinedTest identifier.

Compatibility

Targets Expo SDK 58, React Native 0.88.0, Reanimated 4.7.1, worklets 0.13.0, Skia 2.14.0, and expo-blur 58.0.5.

Android blur uses a BlurTargetView and dimezisBlurViewSdk31Plus, following the Expo SDK 58 BlurView documentation. The dark translucent card provides its surface treatment on platforms without real blur.

Accessibility

All controls have button roles and action labels; side controls are 48 pt and the main button is 76 pt. Touching the ring pauses or resumes. The countdown is hidden from the accessibility tree to prevent repeated digit announcements. Each phase announces its name and duration, and completion is announced once. Reduced motion keeps color fades and removes digit travel, pulse, exercise travel/blur, button morph travel, rotation, and particles.

Demo

The Tidewell demo starts at round 3 with 14 seconds left. Autoplay starts with 2 seconds left and resets every 6.5 seconds, putting the coordinated rest transition within the first three seconds. Full-session timing is still 8 × (20 + 10) = 4:00.

Layout is derived from onLayout: header at 0.06 H, 0.78 W ring centered at 0.38 H, card at 0.66 H, and controls at 0.80 H. At 320×694 the card ends around 546 pt and the controls begin around 555 pt; at 390×844 they have more separation. Header content stays left of the viewer's upper-right close button. Light is a radial shader and blur belongs to the text/material; no opaque effect block sits above content.