Styling the waveform

Colors

Three options control the core colors of the waveform:

Option Default Description
waveColor '#999' The unplayed portion of the waveform
progressColor '#555' The played-back portion (overlaid as a mask)
cursorColor (inherits progressColor) The playback cursor line

waveColor and progressColor each accept:

  • A CSS color string'#ff5500', 'rgba(255,0,0,0.8)', 'coral', etc.
  • An array of CSS color strings — rendered top-to-bottom as vertical bands, one color per equal slice of height.
  • A CanvasGradient — created from a canvas context (see Gradients below).
WaveSurfer.create({
  container: '#waveform',
  waveColor: '#4F4A85',
  progressColor: '#383351',
  cursorColor: '#fff',
})

Bars

Setting barWidth switches from the default continuous line shape to a bar chart style. See the Bars example for a live demo.

Option Type Description
barWidth number Width of each bar in pixels. A value > 0 enables bar mode.
barGap number Space between bars in pixels (defaults to barWidth / 2 if not set).
barRadius number Border radius of each bar in pixels (rounded caps).
barAlign 'top' | 'bottom' Pins bars to the top or bottom edge instead of the center.
barHeight number Vertical scaling factor. 1.0 is normal; 1.5 makes bars taller.
barMinHeight number Minimum height of any bar in pixels — silences never collapse to zero.
WaveSurfer.create({
  container: '#waveform',
  waveColor: '#4F4A85',
  progressColor: '#383351',
  barWidth: 3,
  barGap: 2,
  barRadius: 3,
  barMinHeight: 2,
})

Gradients

Pass a CanvasGradient as waveColor or progressColor for full control over the color sweep. The gradient must be created from a throwaway <canvas> context — wavesurfer will map it onto the real canvas at render time.

// Build a vertical gradient (top → bottom)
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')

const gradient = ctx.createLinearGradient(0, 0, 0, canvas.height * 1.35)
gradient.addColorStop(0, '#656666')  // top
gradient.addColorStop((canvas.height * 0.7) / canvas.height, '#656666')
gradient.addColorStop((canvas.height * 0.7 + 1) / canvas.height, '#ffffff')
gradient.addColorStop((canvas.height * 0.7 + 2) / canvas.height, '#ffffff')
gradient.addColorStop((canvas.height * 0.7 + 3) / canvas.height, '#B1B1B1')
gradient.addColorStop(1, '#B1B1B1')  // bottom

WaveSurfer.create({
  container: '#waveform',
  waveColor: gradient,
  progressColor: 'rgba(100, 0, 100, 0.5)',
})

See the Gradient example for a full working demo.

Gradients use absolute pixel coordinates tied to the canvas height at creation time. If the container is resized (e.g. on a high-DPI display or responsive layout) the gradient will not automatically rescale — you may need to recreate it and call wavesurfer.setOptions({ waveColor: newGradient }) in a resize handler.


Cursor and height

Option Type Default Description
cursorWidth number 1 Width of the playback cursor in pixels. Set to 0 to hide it.
height number | 'auto' 128 Height of the waveform in pixels, or 'auto' to fill the container’s height.
WaveSurfer.create({
  container: '#waveform',
  height: 80,
  cursorWidth: 2,
  cursorColor: '#ff0000',
})

Styling with CSS

Wavesurfer renders its DOM inside a shadow root, so ordinary CSS selectors cannot reach inside it. To style the internal elements from your stylesheet, use the ::part() pseudo-element with the exact part names exposed by the renderer:

::part() name What it targets
scroll The outermost scrollable wrapper
wrapper The inner positioning wrapper that holds canvases and the cursor
canvases The container for all waveform canvas elements
progress The clipped overlay that shows progressColor
cursor The vertical cursor line element
/* Host element itself */
#waveform {
  border-radius: 8px;
  overflow: hidden;
}

/* Cursor via part */
#waveform::part(cursor) {
  border-radius: 0;
  width: 2px;
  background: hotpink;
}

/* Scrollbar on the scroll container */
#waveform::part(scroll)::-webkit-scrollbar {
  height: 4px;
}

The cursorColor and cursorWidth options are the easiest way to style the cursor. Use ::part(cursor) only when you need CSS properties that have no option equivalent (e.g. border-radius, transitions, or custom scrollbar styles).

See the Custom CSS example for a live demo.


Custom render function

For complete drawing control, pass a renderFunction that receives the raw channel peak data and the canvas context. Wavesurfer calls it instead of its built-in line or bar renderer.

Verified signature:

renderFunction: (peaks: Array<Float32Array | number[]>, ctx: CanvasRenderingContext2D) => void
  • peaks — an array of typed arrays, one per rendered channel (usually 1–2 entries).
  • ctx — the 2D context of the canvas to draw on. Its fillStyle is already set to waveColor before your function is called.
WaveSurfer.create({
  container: '#waveform',
  waveColor: '#4F4A85',
  renderFunction: (peaks, ctx) => {
    const { width, height } = ctx.canvas
    const channelData = peaks[0]
    const step = width / channelData.length

    ctx.beginPath()
    for (let i = 0; i < channelData.length; i++) {
      const x = i * step
      const amplitude = Math.abs(channelData[i])
      const barH = amplitude * height
      const y = (height - barH) / 2
      ctx.rect(x, y, step * 0.8, barH)
    }
    ctx.fill()
  },
})

See the Custom render example for a more complete example.

When you provide a renderFunction, wavesurfer still creates a progress canvas by copying your drawing and applying progressColor as a mask on top. This works perfectly with most custom renderers. However, if your function draws both waveform and progress visuals internally, the mask overlay will overdraw your progress color. In that case, ignore progressColor entirely and handle the played/unplayed distinction yourself inside renderFunction — read the current progress via the timeupdate event and conditionally switch ctx.fillStyle.


SoundCloud-style bars

Tall, narrow orange bars with rounded tops, slightly spaced apart:

WaveSurfer.create({
  container: '#waveform',
  waveColor: '#ff5500',
  progressColor: '#cc3300',
  barWidth: 2,
  barGap: 1,
  barRadius: 2,
  barHeight: 1,
  height: 80,
})

Rekordbox-style

Thin flat bars, full height, aligned to the bottom, dark background (set on the container with CSS):

WaveSurfer.create({
  container: '#waveform',  // set background: #1a1a2e in CSS
  waveColor: '#00e5ff',
  progressColor: '#ffffff',
  barWidth: 1,
  barGap: 1,
  barAlign: 'bottom',
  height: 64,
  cursorColor: '#ff4d4d',
})

ChatGPT-style fixed-width recording bars

Uniform, equal-height bars that grow rightward as audio is recorded — achieved via the Record plugin’s live rendering. See the Record plugin page for the scrollingWaveform and continuousWaveform options that produce this effect.

import RecordPlugin from 'wavesurfer.js/dist/plugins/record.js'

const record = wavesurfer.registerPlugin(RecordPlugin.create({
  renderRecordedAudio: false,
}))

// After recording starts, override the render function for uniform bars:
wavesurfer.setOptions({
  barWidth: 4,
  barGap: 2,
  barRadius: 2,
  barHeight: 1,
  waveColor: '#10a37f',
  progressColor: '#10a37f',
  height: 48,
})