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])

Imperative calls

Speed, volume, and seek should be called imperatively — never passed as options — to avoid recreating the instance:

// ❌ Recreates instance on every speed change
useWavesurfer({ audioRate: speed })

// ✅ Imperative — no re-creation
wavesurfer?.setPlaybackRate(speed, true)
wavesurfer?.setVolume(0.5)
wavesurfer?.seekTo(0.5) // 0–1 normalized

API Reference

Parameters

OptionTypeDefaultDescription
containerRefObject<HTMLDivElement>Required. Ref to the container element.
urlstringAudio 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 array.
...restWaveSurferOptionsAny other wavesurfer.js option.

Returns

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

On this page