1
1 Comment

react-modern-audio-player — React audio library with compound-slot layout (recent update added playback speed)

Maintaining an open-source React audio library called react-modern-audio-player and wanted to surface a few open design questions to anyone who's wrestled with media UI in React.

The frame I went with: every built-in control (Progress, Volume, PlayList, PlayButton, SpeedSelector, etc.) is a compound slot. So you mix the preset layout with custom placement instead of swapping the whole component or rebuilding the layout from scratch.

Default full view

Same controls, moved to different grid cells:

Custom grid placement

State is also reachable through an imperative hook, so the UI doesn't have to live inside the player frame:

const { playbackRate, setPlaybackRate, currentTime, isPlaying } = useAudioPlayer();

A11y follows WAI-ARIA APG out of the box — the rate menu uses role="menu" + role="menuitemradio" with aria-checked, and the click-mode Volume uses role="dialog" (tooltip semantics aren't right for an interactive panel). Same shape as video.js and Vidstack.

Optional waveform mode via wavesurfer.js:

Waveform progress mode

Latest 2.3.1 added: playback speed in three surfaces (compound slot, hook API, initial-state field), dropdown customization for Volume and SpeedSelector (triggerType, placement), and a track-swap silence fix on bar-progress players.

Stack: TypeScript-first, Next.js App Router compatible (Server Components), wavesurfer.js for the optional waveform mode, no UI-framework dependency.

Repo, README, full changelog: https://github.com/slash9494/react-modern-audio-player

Open questions for anyone who's built media UIs

  1. Compound-slot pattern vs. preset-only — has anyone tried this pattern (preset layout + per-slot override) for audio/video controls? What broke when you scaled the grid past a few cells? I'm curious whether the resolution order (compound prop > UIContext > component default) holds up under deeper composition.

  2. Discrete-rate selector role — for playback-rate UIs, would you reach for role="menu" + menuitemradio (current choice) or role="combobox" with typeahead? I went with menu because the rate set is small and fixed, but I'm not sure if combobox would feel more natural for keyboard users on the rare 0.5×–2× span.

  3. formatRate callback shape — went through several revisions: (rate: number) => string (current), render prop, hardcoded × suffix, intl.NumberFormat-style options. Which would you reach for in your own code, and why?

Genuinely open to being told I picked the wrong tradeoff on any of these. Drop a take below if you've shipped something adjacent.

on May 1, 2026
  1. 1

    I know how challenging it is to get this type of feedback, so I wanted to offer that I happen to know a few software developers with media UI experience who would likely be happy to answer your questions.