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.

Same controls, moved to different grid cells:

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:

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
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.
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.
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.