About this animation
Related guides
A simulated voice-note player with scrubbing and speed controls, with configurable duration, gradient colors, fonts, and avatar initials.
Why this one
Most voice-message examples are a row of static bars around a play button. This version treats the waveform as the control: every message owns one continuous progress value, the current bar fills precisely, nearby bars lift with the voice envelope, and a scrub cursor exposes an exact time bubble. Playback speed changes through a compact vertical roller, completion clears the waveform with a soft sweep, and the component remains useful without audio or app-specific state.
How it works
- A deterministic seeded envelope creates 42 realistic bars with phrase-level pauses and syllable variation.
- One Reanimated shared value drives played color, partial cursor position, remaining time, scrubbing, and completion.
- A UI-thread frame callback advances the simulation at 1×, 1.5×, or 2× without React updates per frame.
- A pan-or-tap gesture seeks directly; releasing always resumes playback from the chosen position.
- Nearby bars derive a restrained 15% lift and voice pulse from their distance to the cursor.
- Reduced Motion removes pulsing and travel while preserving every state and interaction.
Install
npx expo install expo-linear-gradient @shopify/react-native-skia react-native-gesture-handler react-native-reanimated react-native-workletsIcons: Lucide (ISC).
Usage
import { WaveformVoiceMessage } from './waveform-voice-message';
export function Message() {
return <WaveformVoiceMessage duration={42} avatarInitials="MC" />;
}<WaveformVoiceMessage
duration={18}
gradientColors={['#6D5BFF', '#8B5CF6']}
playedWaveColor="#FFFFFF"
idleWaveColor="rgba(255,255,255,0.34)"
onComplete={() => markAsListened()}
/>Props
| Prop | Type | Default | Description |
|---|---|---|---|
duration | number | 42 | Simulated message length in seconds. |
gradientColors | readonly [string, string] | ['#1C1C22', '#1C1C22'] | Surface gradient. |
controlColor | string | '#F4F4F8' | Playback-control surface. |
iconColor | string | '#17171D' | Play and pause icon color. |
idleWaveColor | string | rgba(255,255,255,0.25) | Unplayed bars. |
playedWaveColor | string | '#FFFFFF' | Played bars and scrub cursor. |
textColor | string | '#FFFFFF' | Time, speed, and avatar text. |
avatarInitials | string | — | Optional trailing avatar initials. |
avatarColor | string | '#383843' | Avatar surface. |
showUnreadDot | boolean | true | Shows a blue dot until playback starts. |
fontFamily | string | System | Interface font. |
monoFontFamily | string | System | Timer font. |
onPlay | () => void | — | Called when playback begins. |
onPause | () => void | — | Called when playback pauses. |
onComplete | () => void | — | Called at the end of playback. |
accessibilityLabel | string | 'Voice message' | Screen-reader description. |
style | StyleProp<ViewStyle> | — | Outer container style. |
testID | string | — | Test identifier. |
The forwarded ref exposes play(), pause(), seekTo(position), cycleSpeed(), and reset().
Compatibility
- Expo SDK 58, React Native 0.88.0, Reanimated 4.7.1, Skia 2.14.0.
- iOS: supported.
- Android: supported.
- Web: supported.
- Works in Expo Go with the SDK-bundled native modules.
Accessibility
The play control exposes Play, Pause, and Skip forward 5 seconds actions. The waveform supports both pan and tap seeking, controls meet a 44-point hit target, and screen readers receive the remaining time. Reduced Motion removes waveform pulsing, animated seeking travel, and spring feedback while retaining readable state changes.




