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. ItsfillStyleis already set towaveColorbefore 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.
Recreating popular looks
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,
})