Skip to content
Native Animation
Heart Rate Live animation preview

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

npx nativeanimation add heart-rate-live
Skia

Heart Rate Live

A heart-rate monitor with rolling BPM and a synchronized ECG sweep, with configurable initial BPM, metrics, trace 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

A heart-rate monitor with rolling BPM and a synchronized ECG sweep, with configurable initial BPM, metrics, trace color, and fonts.

Why this one

The heart and ECG use the same integrated beat phase. Changing BPM changes their rhythm together; pausing freezes the trace without resetting its history. The monitor overwrites samples in place and leaves an 18-point clearing gap ahead of its bright head, rather than scrolling a decorative line.

How it works

  1. onLayout measures the host container; all major sections use fractions of its width and height.
  2. One useFrameCallback advances active time and integrates BPM into a continuous beat phase. A P–QRS–T waveform supplies both the trace samples and the QRS crossing that triggers the 220 ms heart pulse.
  3. A 400-sample ring stores four seconds at 10 ms intervals. Skia draws disjoint path segments, omitting the clearing gap instead of placing an opaque block over the ECG.
  4. BPM rolls on the UI thread. Each numeral column advances only at its own carry and fades digits away from the resting position. Effort-zone position follows an interruptible spring.
  5. Pause stops the shared clock, lowers the monitor to 50%, and changes the accessible control to Resume. A polite announcement updates every ten seconds of active monitoring.
  6. Reduced motion draws a complete static waveform, uses integer BPM, and disables the sweep, glow expansion, and heartbeat.

Install

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

Copy HeartRateLive.tsx, icons.ts, and index.ts into your project. Load your fonts in the host application. For web, initialize CanvasKit before importing this component (Expo's Skia web setup or a deferred WithSkiaWeb boundary). The catalog registration must use its existing withSkia loader.

Icons: Lucide (ISC). All used paths are included in icons.ts.

Usage

import { HeartRateLive } from './heart-rate-live';

export function Workout() {
  return <HeartRateLive initialBpm={128} />;
}
import { useRef } from 'react';
import { HeartRateLive, type HeartRateLiveRef } from './heart-rate-live';

export function CustomWorkout() {
  const monitor = useRef<HeartRateLiveRef>(null);
  return (
    <HeartRateLive
      ref={monitor}
      initialBpm={124}
      maxBpm={180}
      sessionLabel="Evening run · 32:14"
      traceColor="#2DD4BF"
      beatIntensity={0.12}
      waveAmplitude={0.38}
      onBpmChange={(bpm) => saveWorkoutRate(bpm)}
    />
  );
}

saveWorkoutRate represents your application's callback. Call monitor.current?.setBpm(131) from your sensor subscription or simulation, and clean up that subscription in the host. Ref methods: setBpm(number), pause(), resume(). The waveform is illustrative, not sensor ECG data or a diagnostic tool.

Props

PropTypeDefaultDescription
initialBpmnumber128Starting rate, clamped to 40–220.
maxBpmnumber180Maximum rate for five effort zones; protected against zero.
headerReactNode—Application branding slot.
sessionLabelstringWorkout in progressSession description.
metrics{ label; value; unit? }[]Avg 121 bpm, Max 164 bpm, Calories 412Up to three summary cards.
fontFamilystringSystemInterface font.
mediumFontFamilystringfontFamilyStrong label font.
monoFontFamilystringSystemNumeral font; use a monospaced face.
backgroundColorstring#060B0BBackground color.
traceColorstring#2DD4BFWaveform accent.
beatIntensitynumber0.12QRS heart-scale increment.
waveAmplitudenumber0.38Wave height relative to the plot.
onStateChange(state: 'live' | 'paused') => void—Pause/resume notification.
onBpmChange(bpm: number) => void—Accepted target-rate notification.
pauseLabelstringPause heart rate monitorAccessible pause label.
resumeLabelstringResume heart rate monitorAccessible resume label.
styleStyleProp<ViewStyle>—Outer container styling.
testIDstring—Outer test identifier.

Compatibility

Targets Expo SDK 58, React Native 0.88.0, Reanimated 4.7.1, Worklets 0.13.0, and Skia 2.14.0. Uses SDK 58's BlurTargetView with dimezisBlurViewSdk31Plus; older Android versions receive its translucent fallback. Compatible with the installed Expo Go modules; custom native projects need matching native dependencies.

The component is self-contained; only the demo imports the catalog's fonts and adds the Tidewell brand and simulated effort curve.

Accessibility

Both the heart and the 64-point pause control support ordinary taps, button roles, and state-dependent labels. The heart exposes its paused selection state. Decorative rolling digits are hidden from assistive technology. A readable accessibilityLiveRegion="polite" announces “128 beats per minute” at ten-second intervals; VoiceOver announcement delivery should be checked on a device. Reduced motion presents a complete static ECG and unanimated integer values.

Demo timeline

Autoplay starts at 124 BPM and requests 131 at 1.8 seconds, settling by 2.35 seconds. The monitor sweeps immediately, with every heart impulse tied to its QRS. Subsequent effort targets change every 3.5 seconds across 118–142 BPM. Pausing also pauses the demo schedule; resume continues the same sample history.

Layout: at 390×844 and 320×694, the header stays left of the viewer's upper-right close control; the hero, zone, ECG, cards, and footer have distinct vertical bands. Numerals scale to 64 points on the smaller width. The 64-point cards end before the footer at both sizes. Glows are radial shaders, the ECG glow blurs the actual path, and the sweep gap omits geometry; no opaque overlay fakes an effect.

Customization notes

The incoming heart-rate data determines beat timing; a cosmetic duration would misrepresent that data.