About this animation
A streak counter with an animated flame and weekly habit strip, with configurable streak, day labels, accent color, and fonts.
How it works
- Skia draws a continuously deforming flame silhouette. A procedural shader combines two scales of moving noise with ribbons of heat inside the silhouette. Blurred paths and a radial halo add depth without rectangular glow overlays.
- One completion progress drives a small anticipation, a restrained flare, rising embers, the digit roll, and today's illuminated check. The interruptible completion spring uses explicit mass and near-critical damping.
- Each digit has its own measured window. Unchanged columns stay still; changed digits fade as they roll, including carries such as 19 → 20. React does not update per frame.
- The week shows completed days, today's outlined indicator, and future days. Today fills as the flame flares; the action crossfades to its completed label.
onLayoutselects a stacked or compact horizontal composition. Content can scroll at large accessibility text sizes. The host owns safe-area insets.
The demo waits for its first nonzero layout, holds the starting count for 1.2 seconds, then celebrates the change from 12 to 13 within the first 3 seconds. It repeats every 4.3 seconds with the same pause after reset. All seven weekday labels remain visible, including today's label. Tap its brand header to switch between dark and light treatments.
Install
npx expo install @shopify/react-native-skia expo-linear-gradient react-native-reanimated react-native-workletsOn web, initialize CanvasKit before loading this component. No external images are required.
Usage
import StreakFlameCounter from './streak-flame-counter';
export function HabitScreen() {
return <StreakFlameCounter streak={19} goalLabel="Practice for 15 minutes" />;
}import { useRef } from 'react';
import StreakFlameCounter, { type StreakFlameCounterRef } from './streak-flame-counter';
export function DailyGoal() {
const streakRef = useRef<StreakFlameCounterRef>(null);
return (
<StreakFlameCounter
ref={streakRef}
streak={28}
colorScheme="light"
accentColor="#E9813D"
fontFamily="YourSansFont"
mediumFontFamily="YourMediumFont"
numberFontFamily="YourMonoFont"
onComplete={() => saveDailyProgress()}
/>
);
}complete() adds one day and calls onComplete once after settling. Repeated calls are ignored until reset(). Reset interrupts the spring, cancels a pending completion callback, and restores the starting value. The host persists its actual streak separately.
Props
| Prop | Type | Default | Purpose |
|---|---|---|---|
streak | number | 12 | Starting whole-day count; clamped to 0–999999. |
streakLabel | string | day streak | Counter unit. |
goalLabel | string | Make time for your daily goal | Supporting habit description. |
actionLabel | string | Complete today | Primary action. |
completedLabel | string | Done for today | Completed action. |
replayLabel | string | Replay | Reset action and accessible label. |
weekLabel | string | THIS WEEK | Weekly section heading. |
dayLabels | readonly string[] | Sun through Sat | Up to seven weekday labels in display order. |
todayIndex | number | 4 | Current day in that order. |
colorScheme | light | dark | auto | dark | Complete theme, including text, surfaces, and borders. |
backgroundColor | string | Theme-derived | Background override; use transparent to embed. |
accentColor | string | #FF8A42 | Flame, day fill, and action accent. |
flameHighlightColor | string | #FFF0BD | Hottest flame material. |
textColor, mutedColor | string | Theme-derived | Main and supporting text. |
surfaceColor, borderColor | string | Theme-derived | Weekly surface and border. |
actionTextColor | string | #211109 | Action text contrast. |
fontFamily, mediumFontFamily | string | System font | Interface font families. |
numberFontFamily | string | Medium/system font | Tabular digit font; demo uses Geist Mono. |
onComplete | () => void | — | Called when completion settles. |
accessibilityLabel | string | Action label | Primary accessible action label. |
style | StyleProp<ViewStyle> | — | Root container styling. |
testID | string | — | Root ID; actions append -complete and -reset. |
Compatibility
Expo SDK 58, React Native 0.88.0, Reanimated 4.7.1, Worklets 0.13.0, and Skia 2.14.0. Uses the installed Expo Go modules. iOS, Android, and web are supported. The shader has a gradient fallback if compilation is unavailable.
Accessibility
Actions have button roles, accessible labels, and at least 48-point touch areas. A single accessible counter announces the completed count; decorative digit copies and flame are hidden. Reduced Motion freezes the flame, removes embers and spatial effects, and fades the count and weekday state over 180 ms. Text scaling remains enabled, with a scroll fallback for larger content.



