npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

react-native-waveforms

v0.1.3

Published

Cross-platform audio waveform visualizer for React Native, Expo and Web. Static, live recording and playback modes.

Readme

react-native-waveforms

Status: 0.1.x - public release. Future fixes ship as 0.1.x patches.

📖 Documentation + interactive playgrounds: waveforms.arbeetrate.com

Cross-platform audio waveform visualizer for React Native, Expo and Web. Static, live recording and live playback modes - rendered with react-native-svg and animated on the UI thread with react-native-reanimated.

The package is rendering-only - you bring the audio data (an amplitude array, a sample callback, or a playback position) and the component draws it. No expo-av / expo-audio dependency is pulled in, so you can pair it with whatever audio engine you already use.

Features

  • Three components: <Waveform> (static), <PlayerWaveform> (playback progress), <RecorderWaveform> (live samples).
  • Three built-in renderers: bars, line, area - or pass your own.
  • UI-thread animations via Reanimated worklets; no per-frame React renders for playback or recording.
  • Hover (web) / tap & drag (native) interaction with a customisable active bar and onActiveSampleChange callback.
  • Recorder modes: scroll (bars slide horizontally) and morph (bars stay in place, heights animate).
  • Recorder edge effects: fadeIn / fadeOut (alpha fade) and growIn / growOut (height envelope).
  • Linear gradient fills via the WaveformColor API on <Waveform>, <PlayerWaveform> and <RecorderWaveform>.
  • Web support via react-native-web / Expo Web - same API, native DOM events.

Platforms

  • iOS
  • Android
  • Web (react-native-web / Expo Web)

Installation

# expo
npx expo install react-native-waveforms react-native-svg react-native-reanimated react-native-worklets

# bare RN
npm install react-native-waveforms react-native-svg react-native-reanimated react-native-worklets
# or
yarn add react-native-waveforms react-native-svg react-native-reanimated react-native-worklets

Follow the setup guide for each peer dependency:

Peer dependencies

| Package | Version | | ------------------------- | ---------- | | react | >=18 | | react-native | >=0.81 | | react-native-reanimated | >=4.0.0 | | react-native-svg | >=15 | | react-native-worklets | >=0.3.0 |

Usage

Static - <Waveform>

🎮 Live playground →

Render a fixed amplitude array. Amplitudes are expected in [0, 1] by default; use inputRange to remap from another scale (e.g. [-160, 0] dBFS).

import { Waveform } from 'react-native-waveforms';

<Waveform
  samples={amplitudes}      // readonly number[] in [0, 1]
  width={320}
  height={80}
  color="#2563eb"
  gap={2}
  rounded
/>

Switch renderer:

<Waveform samples={amplitudes} width={320} height={80} renderer="line" strokeWidth={1.5} />
<Waveform samples={amplitudes} width={320} height={80} renderer="area" fillOpacity={0.85} />

Gradient fills - any color-style prop (color, progressColor, activeColor) accepts a WaveformColor union: a CSS string or a LinearGradientSpec. <Waveform> and <PlayerWaveform> render gradients via SVG <LinearGradient>; <RecorderWaveform> samples the gradient once per bar slot along the x-axis (see API › WaveformColor and Known issues).

// Two-stop shorthand (left → right by default)
<Waveform
  samples={amplitudes}
  width={320}
  height={80}
  color={{ type: 'linear', from: '#3b82f6', to: '#a855f7' }}
/>

// Multi-stop, vertical
<Waveform
  samples={amplitudes}
  width={320}
  height={80}
  renderer="area"
  color={{
    type: 'linear',
    direction: 'vertical',
    stops: [
      { offset: 0, color: '#0ea5e9' },
      { offset: 1, color: '#1e3a8a' },
    ],
  }}
/>

Hover / tap interaction (turns on once activeColor is set):

<Waveform
  samples={amplitudes}
  width={320}
  height={80}
  color="#2563eb"
  activeColor="#f97316"
  activeScale={1.8}        // bars: grow active bar 80%
  activeTransitionMs={180} // web only
  onActiveSampleChange={(index, value) => {
    // index === null on leave / release
  }}
/>

Playback - <PlayerWaveform>

🎮 Live playground →

Wraps <Waveform> with a UI-thread animated progress fill. Drive it with a progress value (01) or a positionMs + durationMs pair.

import { PlayerWaveform } from 'react-native-waveforms';

<PlayerWaveform
  samples={amplitudes}
  width={320}
  height={80}
  color="#cbd5e1"
  progressColor="#2563eb"
  isPlaying
  positionMs={currentMs}
  durationMs={totalMs}
/>

When isPlaying is true the progress animates smoothly toward the next position on the UI thread, so dropped JS frames don't stutter the bar.

Pass an onSeek callback to make the waveform interactive. Users can tap or press-and-drag to scrub; the played fill follows the finger live, and onSeek fires once on release with the new progress in [0, 1]. While isPlaying is true, playback resumes from the new position automatically.

const [progress, setProgress] = useState(0);

<PlayerWaveform
  samples={amplitudes}
  width={320}
  height={80}
  color="#cbd5e1"
  progressColor="#2563eb"
  isPlaying={isPlaying}
  positionMs={positionMs}
  durationMs={totalMs}
  activeColor="#1d4ed8"     // optional: highlight the bar under the finger
  onSeek={(p) => {
    setProgress(p);
    audio.seekTo(p * totalMs);
  }}
/>

Live recording - <RecorderWaveform>

🎮 Live playground →

Imperative API: keep a ref, call push(amplitude) whenever your meter ticks (mic level, peak meter, etc.).

import { useRef } from 'react';
import {
  RecorderWaveform,
  type RecorderWaveformHandle,
} from 'react-native-waveforms';

const ref = useRef<RecorderWaveformHandle>(null);

// somewhere in your meter callback:
ref.current?.push(0.42); // any number in [0, 1]
ref.current?.push([0.1, 0.2, 0.3]); // batch push also accepted
ref.current?.reset(); // clear buffer

<RecorderWaveform
  ref={ref}
  width={320}
  height={80}
  color="#dc2626"
  baseline="bottom"
  transition="scroll"   // or "morph"
  transitionDuration={200}
  barWidth={3}
  gap={2}
  rounded
/>

API

<Waveform>

| Prop | Type | Default | Notes | | ---------------------- | --------------------------------------- | --------- | ---------------------------------------------------------------------- | | samples | readonly number[] | required | Amplitudes; default range [0, 1]. | | width | number | required | SVG width. | | height | number | required | SVG height. | | color | WaveformColor | '#000' | CSS colour string or LinearGradientSpec. | | renderer | 'bars' \| 'line' \| 'area' \| fn | 'bars' | Custom: (props: RendererProps) => ReactNode. | | inputRange | [number, number] | [0, 1] | Re-maps samples into [0, 1]. | | barWidth | number | auto | Bars only. | | gap | number | 1 | Bars only. | | rounded | boolean \| number | false | Bars only - true = pill, number = explicit radius. | | baseline | 'center' \| 'bottom' | 'center'| Bars only. | | strokeWidth | number | 1 | Line / area. | | fillOpacity | number | 1 | Area only. | | activeColor | WaveformColor | - | Enables hover / tap highlight; gradients accepted. | | activeScale | number | 1 | Bars only - width multiplier for the active bar. | | activePushRange | number | auto | Bars only - neighbours pushed away from the active bar (linear decay). | | activeTransitionMs | number | 150 | Web only - CSS transition duration; native snaps. | | onActiveSampleChange | (index, sample) => void | - | index === null on leave / release. |

<PlayerWaveform>

Inherits all <Waveform> props, plus:

| Prop | Type | Default | Notes | | ------------------- | --------- | --------- | ------------------------------------------------------------------ | | progressColor | WaveformColor | #2563eb | Played-portion colour; gradients accepted. | | progress | number | - | 01. Takes precedence over positionMs / durationMs. | | positionMs | number | - | Current playback position in ms. | | durationMs | number | - | Total duration in ms. | | isPlaying | boolean | false | When true, progress animates on the UI thread to next frame. | | animationDuration | number | 200 | Transition duration between progress values, in ms. | | onSeek | (progress: number) => void | - | Fires on release after a tap or drag. Providing it enables the touch / drag overlay; resumes playback from the new position when isPlaying is true. |

<RecorderWaveform>

Inherits <Waveform> props except samples (you push imperatively), plus:

| Prop | Type | Default | Notes | | --------------------- | --------------------------------------- | ----------- | ----------------------------------------------------------------------------- | | capacity | number | auto | Bar slots in the buffer; computed from width, barWidth, gap if omitted. | | initialSamples | readonly number[] | - | Optional warm-start data. | | transition | 'scroll' \| 'morph' | 'scroll' | Slide bars vs. animate heights in place. | | direction | 'right' \| 'left' | 'right' | Edge new samples enter from. | | prefill | boolean | true | Pre-fill buffer with zeros so animation starts on first push. | | transitionDuration | number | 200 | Per-sample animation duration in ms. | | transitionEasing | EasingFunction \| EasingFactory | linear | From react-native-reanimated. | | fadeIn | number | 0 | Bars at the entry edge that fade in (alpha 0→1). Pure opacity. Try 25. | | fadeOut | number | 0 | Bars at the exit edge that fade out (alpha 1→0). Pure opacity. | | growIn | number | 0 | Bars at the entry edge that grow in height from 0 → full as they shift in. | | growOut | number | 0 | Bars at the exit edge that shrink in height from full → 0. | | smoothScroll | boolean | true | scroll mode only. | | scrollDuration | number | - | Deprecated - use transitionDuration. |

Imperative ref handle:

type RecorderWaveformHandle = {
  push: (amplitude: number | readonly number[]) => void;
  reset: () => void;
};

WaveformColor

type GradientStop = { offset: number; color: string; opacity?: number };

type LinearGradientSpec = {
  type: 'linear';
  /** `'horizontal'` (default) = left → right; `'vertical'` = top → bottom. */
  direction?: 'horizontal' | 'vertical';
  /** Two-stop shorthand. Ignored when `stops` is provided. */
  from?: string;
  to?: string;
  /** Explicit stops; takes precedence over `from` / `to`. */
  stops?: readonly GradientStop[];
};

type WaveformColor = string | LinearGradientSpec;

<Waveform> and <PlayerWaveform> paint gradients via SVG <LinearGradient>, so any direction and any number of stops is honoured. <RecorderWaveform> uses View.backgroundColor per bar - it samples the gradient once per slot along the x-axis and treats vertical as horizontal sampling. Recorder gradients work best with hex (#rgb, #rrggbb, #rrggbbaa) or rgb(...) / rgba(...) colour strings; named CSS colours fall back to the nearest stop without interpolation.

Custom renderers

Any function matching (props: RendererProps) => ReactNode can be passed as renderer. The built-in BarsRenderer, LineRenderer and AreaRenderer are also exported if you want to compose them.

import { BarsRenderer, type WaveformRenderer } from 'react-native-waveforms';

const MyRenderer: WaveformRenderer = (props) => (
  <BarsRenderer {...props} barWidth={4} rounded />
);

Known issues

  • Web - <RecorderWaveform> growIn / growOut + transition="scroll". On web the height envelope is computed per slot and re-interpolated via a requestAnimationFrame loop while the wrapper translates. Some browsers still show subtle stepping in the entry / exit zones during fast pushes. Until this is smoother, the example app gates the scroll + grow demos to native only. Workarounds: use transition="morph" (smooth on web), use fadeIn / fadeOut instead (CSS mask on web - always smooth), or stick to scroll-only / morph-only without grow on web.

Pairing with an audio engine

react-native-waveforms doesn't record or decode audio - it visualises numbers. Common pairings:

  • expo-audio - listen to metering updates and push() them into the recorder.
  • react-native-audio-recorder-player - feed metering from addRecordBackListener.
  • Web - MediaStreamAudioContext.createAnalyser() → push the average amplitude.

The example app in this repo includes a working web microphone demo (example/src/useMicMeter.web.ts).

Contributing

License

MIT - see LICENSE.


Scaffolded with create-react-native-library.