audio-player

Platform audio player that wraps native <audio> with custom controls in shadow DOM. Progressive enhancement — native audio works without JS.

Overview

A platform-native audio player that wraps <audio> in light DOM and renders custom controls in shadow DOM. The native player is always the fallback — if JS is unavailable or the component fails, <audio controls> works normally.

Pair with <audio-visualizer> for canvas-based frequency or waveform visualization.

Single Track

<audio-player> <audio controls> <source src="track.ogg" type="audio/ogg"> <source src="track.mp3" type="audio/mpeg"> <p><a href="track.mp3" download>Download track</a></p> </audio> </audio-player>

Playlist Mode

Include a <details> with <ol class="track-list"> inside the component. Clicking a track updates the audio source and plays it.

<audio-player> <audio controls> <source src="tracks/01-opening.mp3" type="audio/mpeg"> <p><a href="tracks/01-opening.mp3" download>Download track 1</a></p> </audio> <details> <summary>Track Listing</summary> <ol class="track-list"> <li data-audio-active> <a href="tracks/01-opening.mp3">01. Opening Theme</a> <span class="track-meta"><time datetime="PT2M14S">2:14</time></span> </li> <li> <a href="tracks/02-main.mp3">02. Main Theme</a> <span class="track-meta"><time datetime="PT3M45S">3:45</time></span> </li> <li data-audio-favorite> <a href="tracks/03-ending.mp3">03. Ending Theme</a> <span class="track-meta"><time datetime="PT4M12S">4:12</time></span> </li> </ol> </details> </audio-player>

Shuffle and Loop

<audio-player shuffle loop> <audio controls> <source src="tracks/01.mp3" type="audio/mpeg"> </audio> <details open> <summary>Shuffle Playlist</summary> <ol class="track-list"> <li data-audio-active><a href="tracks/01.mp3">Track 1</a></li> <li><a href="tracks/02.mp3">Track 2</a></li> <li><a href="tracks/03.mp3">Track 3</a></li> </ol> </details> </audio-player>

Attributes

AttributeTypeDescription
autoplayBooleanStart playing on load (subject to browser autoplay policy)
loopBooleanLoop single track or entire playlist
shuffleBooleanRandomize playlist order on track advance

CSS Custom Properties

PropertyDefaultDescription
--audio-player-accentvar(--color-primary)Play button, slider thumbs, and progress fill color
--audio-player-bgvar(--color-surface)Player background and slider thumb border color
--audio-player-radiusvar(--radius-m)Player border radius
--audio-player-textvar(--color-text)Track title and controls text color
--audio-player-bordervar(--color-border)Player border and slider track color
--audio-player-shadownonePlayer box shadow (opt-in)
--audio-player-paddingvar(--size-xs) var(--size-s)Controls area padding
audio-player { --audio-player-accent: oklch(60% 0.2 30); --audio-player-bg: oklch(15% 0.02 260); --audio-player-text: oklch(95% 0 0); --audio-player-border: oklch(30% 0.02 260); --audio-player-shadow: var(--shadow-md); --audio-player-radius: var(--radius-l); --audio-player-padding: var(--size-s) var(--size-m); }

Dark Mode

The player adapts automatically to light and dark mode via design tokens. All colors resolve through CSS custom properties that respond to theme changes. Wrap any ancestor in data-mode="dark" or let the system preference apply.

<div data-mode="dark"> <audio-player> <audio controls> <source src="track.mp3" type="audio/mpeg"> </audio> </audio-player> </div>

Elevated Card Style

Opt in to a shadow with --audio-player-shadow for a raised card look.

<audio-player style=" --audio-player-shadow: var(--shadow-md); --audio-player-padding: var(--size-s) var(--size-m); "> <audio controls> <source src="track.mp3" type="audio/mpeg"> </audio> </audio-player>

Shadow Parts

PartDescription
playerOuter player container
controlsControls row
play-buttonPlay/pause button
timelineSeek range input
volumeVolume range input
track-infoTrack title and time display

Events

EventDetail
audio-player:play{ currentTime, src }
audio-player:pause{ currentTime }
audio-player:ended{ src }
audio-player:track-change{ src, title }
const player = document.querySelector('audio-player'); player.addEventListener('audio-player:play', (e) => { console.log('Playing:', e.detail.src); }); player.addEventListener('audio-player:track-change', (e) => { console.log('Now playing:', e.detail.title); });

Keyboard Shortcuts

KeyAction
SpacePlay / Pause
Left ArrowSeek back 10 seconds
Right ArrowSeek forward 10 seconds
MToggle mute

Progressive Enhancement

The <audio> element lives in light DOM. Before JS loads, native controls render. After the component upgrades, native controls are hidden and custom chrome takes over. If the component is removed from the DOM, native controls are restored.

<!-- Without JS: native <audio> controls render and work. Track list links navigate to the audio file. --> <audio-player> <audio controls> <source src="track.mp3" type="audio/mpeg"> <p><a href="track.mp3" download>Download track</a></p> </audio> </audio-player>

Track List Data Attributes

<li> elements in .track-list use data attributes for state:

AttributeSet byMeaning
data-audio-activeComponent + authorCurrently loaded/playing track
data-audio-playedComponentTrack has been played this session
data-audio-favoriteAuthorEditorially marked as a highlight

Accessibility

  • All controls have aria-label attributes
  • Controls group has role="group" with label
  • Keyboard navigable with documented shortcuts
  • Respects prefers-reduced-motion
  • Falls back to fully accessible native <audio> without JS

Companion: audio-visualizer

Pair with <audio-visualizer> for canvas-based frequency or waveform display. The visualizer is a separate component — it connects to any <audio> via the for attribute.

Related