A Mantine-native audio player for React with waveform visualisation and a live spectrum analyser, built on the Web Audio API. Same three-layer architecture as Mantine Video — drop-in
<Audio />, composable compound API, headlessuseAudiohook — but designed for a medium that has no frame to look at.
Introduction
Mantine Video shipped last week. The next obvious step was to give audio the same treatment — a polished default player, a composable control bar, a headless hook for fully custom UIs. The catch is that audio has no frame to look at: a 30-second video communicates its content the instant you hit play, while a 30-second audio file looks like a thin grey line. So the new component does the opposite of what most React audio players do — it makes the audio look at itself: decoded waveform on <canvas>, live FFT spectrum on the analyser node, every part theme-aware.
What is mantine-audio?
@gfazioli/mantine-audio wraps the native HTML <audio> element with three layers, layered from "drop-in" to "fully custom":
- A polished default
<Audio />factory with four built-in variants (overlay,minimal,floating,bordered) and five sizes (xs–xl), all CSS-driven via[data-variant]/[data-size]attributes. - A composable compound API — ten sub-components you can reorder, replace or restyle:
Audio.Controls,Audio.PlayButton,Audio.SkipButton,Audio.Timeline,Audio.TimeDisplay,Audio.MuteButton,Audio.VolumeSlider,Audio.SpeedControl,Audio.Waveform,Audio.Spectrum. - A fully headless
useAudiohook with 16 state values, 12 actions, the decoded peaks, theAnalyserNode, andcurrentSrc— to drive a 100% custom UI on top of a plain<audio>element.
It targets Mantine 9 and React 19, is TypeScript-first, and has zero runtime dependencies beyond React, Mantine, and @tabler/icons-react (optional peer).
✨ Key Features
Waveform visualisation
Audio.Waveform decodes the audio file once via fetch + decodeAudioData, downsamples to waveformSamples peaks (default 512), and renders them on a <canvas>. Click or drag to seek. The played / unplayed boundary tracks the playhead at 60fps via requestAnimationFrame, not at the slower timeupdate cadence (~4Hz) — so the colour transition moves smoothly with the audio.
<Audio src="/track.mp3" variant="floating">
<Audio.Waveform height={80} mirrorGap={0} />
<Audio.Controls />
</Audio>
mirror (default true) draws bars symmetrically around the centerline for the classic Audacity look. mirrorGap controls the divider thickness — 0 for a continuous filled shape, >0 for a visible centerline.
Live spectrum analyser
Audio.Spectrum is a frequency-domain visualiser driven by Web Audio's AnalyserNode. The AudioContext is lazy-initialised on the first play (autoplay-policy compliant), and the bars react to the music in real time. Two color modes — solid (uniform fill) and gradient (vertical fade to transparent). Bin distribution is proportional: no high-frequency bins are dropped when barCount doesn't divide fftSize evenly.
<Audio src="/track.mp3" color="grape">
<Audio.Spectrum barCount={48} colorMode="gradient" />
<Audio.Controls />
</Audio>
Headless useAudio hook
When the default layout doesn't fit, drop the compound layer entirely and drive your own UI:
import { useAudio } from '@gfazioli/mantine-audio';
function MyPlayer() {
const { playing, currentTime, duration, peaks, analyser, toggle, audioRef } = useAudio({
src: '/track.mp3',
});
return (
<div>
<audio ref={audioRef} crossOrigin="anonymous" />
<button onClick={toggle}>{playing ? 'Pause' : 'Play'}</button>
<span>{currentTime.toFixed(1)} / {duration.toFixed(1)}</span>
</div>
);
}
The hook returns 16 state values (playing, currentTime, duration, volume, muted, playbackRate, peaks, analyser, audioContext, currentSrc, …) and 12 action functions (play, pause, toggle, seek, seekBy, setVolume, mute, unmute, toggleMute, setPlaybackRate, …).
scrubSound — hear the seek
Most audio players pause during a seek-drag, then jump to the new position. scrubSound keeps the audio playing during the drag — you hear short snippets of audio as the cursor moves, the way Audacity, Adobe Audition, and iTunes do it. And it works even from a paused state: the player starts on mousedown, plays through the drag, and pauses again on release.
<Audio src="/track.mp3" scrubSound>
<Audio.Waveform height={80} />
<Audio.Controls />
</Audio>
The flag propagates via React context, so both Audio.Timeline and Audio.Waveform honour it automatically. Individual sub-components can still override their own scrubSound if you want fine-grained control.
Multiple sources with runtime fallback
For cross-browser compatibility, adaptive bitrate, or media-query switching, pass sources instead of src. The browser picks the first entry whose type it can play via canPlayType():
<Audio
sources={[
{ src: '/track.aac', type: 'audio/aac' }, // iOS preferred
{ src: '/track.ogg', type: 'audio/ogg' }, // Firefox preferred
{ src: '/track.mp3', type: 'audio/mpeg' }, // universal fallback
]}
fallbackSrc="/last-resort.mp3"
/>
src and sources are mutually exclusive — a dev-mode warning is logged if both are set. fallbackSrc mirrors the equivalent prop on Mantine Image and kicks in when every entry in sources errors at runtime.
asBackground preset
Turn the same player into an ambient hero / section background in one prop:
<Box pos="relative" h="100vh">
<Audio src="/ambient.mp3" asBackground loop muted />
<YourHeroContent />
</Box>
controls and shortcuts are disabled by default in this mode, and a small floating mute toggle is rendered in the bottom-right corner (disable with backgroundMuteButton={false}).
Sizes & layouts
size scales 11 CSS variables at once — padding, gap, icon size, play button, action button, timeline thumb, time display width, volume slider width, font size. Five presets cover most needs:
<Audio size="xs" src="/voice-note.mp3" variant="minimal" /> // chat bubble
<Audio size="sm" src="/podcast.mp3" variant="bordered" /> // card
<Audio size="lg" src="/album.mp3" variant="floating" /> // hero card
<Audio size="xl" src="/master.wav" variant="overlay" /> // studio scrub
And because the default <Audio.Controls /> is just a horizontal row, you can drop it (controls={false}) and arrange the sub-components however you like — vertical stack, custom skip ranges, separate volume slider section — using any layout primitive from @mantine/core.
Keyboard shortcuts
When the player has focus:
- Space / K — play/pause
- J / L — seek backward / forward 10s
- ← / → — seek backward / forward 5s
- ↑ / ↓ — volume ±5%
- M — toggle mute
-
>/<— speed ±0.25×
🚀 Getting Started
npm install @gfazioli/mantine-audio
import { Audio } from '@gfazioli/mantine-audio';
import '@gfazioli/mantine-audio/styles.css';
function App() {
return <Audio src="/track.mp3" />;
}
That's the entire setup — you now have a polished, accessible audio player with timeline, time display, mute, volume slider, and speed control.
🎨 Styles API
Audio exposes the full Mantine Styles API: every sub-component is a styles-api selector you can target via classNames or styles. Eighteen selectors in total — root, audio, controls, controlBar, playButton, timeline, timelineBuffered, timeDisplay, muteButton, skipButton, volumeSlider, speedControl, waveform, waveformCanvas, waveformHover, spectrum, spectrumCanvas, backgroundMuteButton.
Nine CSS variables let you re-skin the whole player from the outside:
.mantine-Audio-root {
--audio-color: hotpink;
--audio-waveform-color: rgba(255, 255, 255, 0.15);
--audio-waveform-played-color: hotpink;
--audio-spectrum-bar-color: hotpink;
--audio-spectrum-bar-color-fade: transparent;
}
Five data-* modifiers (data-variant, data-playing, data-paused, data-ended, data-as-background) are available on the root for state-based styling.
Live Demo & Documentation
The full docs site has interactive demos for every feature — usage, configurator, sizes, compound API, custom layout, waveform with a live prop configurator, spectrum with another configurator, volume & speed, asBackground, multiple sources, headless usage, keyboard shortcuts, styles API, and a "use cases" showcase with real-world layouts (inline voice-note bubble, podcast card with cover, mini sticky bar, studio scrub).
→ https://gfazioli.github.io/mantine-audio
Links
- 📦 npm: https://www.npmjs.com/package/@gfazioli/mantine-audio
- 📖 Documentation: https://gfazioli.github.io/mantine-audio
- 🔗 GitHub: https://github.com/gfazioli/mantine-audio
- 🌐 Mantine Extensions Hub: https://mantine-extensions.vercel.app/


Top comments (0)