useWavesurfer

The primary React hook for wavesurfer.js. Wraps instance creation, state tracking, CSS variable resolution, and shared defaults into a single call.

Overview

useWavesurfer is the lower-level alternative to WavesurferPlayer. Use it when you need full control over the container element, want to co-locate the waveform inside a complex layout, or need to read reactive state (isPlaying, currentTime, etc.) directly in your component.

Usage

import { useRef } from "react"
import { useWavesurfer } from "@/lib/wave-cn"

export function MyPlayer() {
  const containerRef = useRef<HTMLDivElement | null>(null)

  const { wavesurfer, isReady, isPlaying, currentTime } = useWavesurfer({
    container: containerRef,
    url: "/coastline.mp3",
  })

  return (
    <div>
      <div ref={containerRef} />
      <button onClick={() => wavesurfer?.playPause()} disabled={!isReady}>
        {isPlaying ? "Pause" : "Play"}
      </button>
      <span>{currentTime.toFixed(2)}s</span>
    </div>
  )
}

Overriding defaults

Any option overrides WAVESURFER_DEFAULTS. Passing undefined gracefully falls back to the default — no need to repeat shared values in every component.

// All WAVESURFER_DEFAULTS apply
useWavesurfer({ container, url })

// Override only what you need
useWavesurfer({ container, url, height: 120, barWidth: 5 })

With plugins

Always memoize the plugins array — the hook uses array reference equality to decide whether to recreate the instance.

import { useMemo } from "react"
import { useWavesurfer } from "@/lib/wave-cn"
import ZoomPlugin from "wavesurfer.js/dist/plugins/zoom.esm.js"

const plugins = useMemo(
  () => [ZoomPlugin.create({ scale: 0.5, maxZoom: 1000 })],
  [],
)

const { wavesurfer, isReady } = useWavesurfer({ container, url, plugins })

For plugins that require the instance to already exist, register them in a useEffect:

import { useEffect } from "react"
import { useWavesurfer } from "@/lib/wave-cn"
import RecordPlugin from "wavesurfer.js/dist/plugins/record.esm.js"

const { wavesurfer } = useWavesurfer({ container, url: "" })

useEffect(() => {
  if (!wavesurfer) return
  wavesurfer.registerPlugin(RecordPlugin.create({ scrollingWaveform: true }))
}, [wavesurfer])

What recreates the instance

Only url, media, plugins, peaks, duration, sampleRate, backend and mediaControls recreate the instance. Every other option (height, barWidth, colors, audioRate, …) is applied in place through wavesurfer.setOptions() and never re-decodes the audio.

plugins and peaks are compared by reference, so keep them in useMemo.

Imperative calls

Playback state (speed, volume, position) is still best driven imperatively:

wavesurfer?.setPlaybackRate(speed, true)
wavesurfer?.setVolume(0.5)
wavesurfer?.seekTo(0.5) // 0–1 normalized

If you need play/pause/seek/volume state with matching actions, use useWavePlayer together with WavesurferPlayer instead.

API Reference

Parameters

OptionTypeDefaultDescription
containerRefObject<HTMLDivElement>—Required. Ref to the container element.
urlstring—Wave file URL.
waveColorstringvar(--muted-foreground)Accepts CSS vars, hex, hsl, oklch.
progressColorstringvar(--primary)Same formats as waveColor.
heightnumber64Canvas height in px.
barWidthnumber3Bar width in px.
barGapnumber2Gap between bars in px.
barRadiusnumber2Bar border radius.
minPxPerSecnumber1Minimum pixels per second (zoom level).
pluginsGenericPlugin[][]Memoized plugin templates (cloned on each create).
peaksArray<Float32Array | number[]>—Precomputed peaks, memoized.
durationnumber—Duration in seconds, used with peaks.
...restWaveSurferOptions—Any other wavesurfer.js option.

Returns

PropertyTypeDescription
wavesurferWaveSurfer | nullThe live instance. null until mounted.
isReadybooleantrue after the ready event fires.
isPlayingbooleantrue while wave is playing.
currentTimenumberCurrent playback position in seconds.

On this page