Welcome to Vanilla Breeze
This bell pulls live notifications from /go/notify/messages — the same contract documented at /docs/concepts/service-contracts/. Static articles like this one are the no-JS / no-backend fallback.
This bell pulls live notifications from /go/notify/messages — the same contract documented at /docs/concepts/service-contracts/. Static articles like this one are the no-JS / no-backend fallback.
Platform audio player that wraps native <audio> with custom controls in shadow DOM. Progressive enhancement — native audio works without JS.
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.
<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>
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>
<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>
| Attribute | Type | Description |
|---|---|---|
autoplay | Boolean | Start playing on load (subject to browser autoplay policy) |
loop | Boolean | Loop single track or entire playlist |
shuffle | Boolean | Randomize playlist order on track advance |
| Property | Default | Description |
|---|---|---|
--audio-player-accent | var(--color-primary) | Play button, slider thumbs, and progress fill color |
--audio-player-bg | var(--color-surface) | Player background and slider thumb border color |
--audio-player-radius | var(--radius-m) | Player border radius |
--audio-player-text | var(--color-text) | Track title and controls text color |
--audio-player-border | var(--color-border) | Player border and slider track color |
--audio-player-shadow | none | Player box shadow (opt-in) |
--audio-player-padding | var(--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);}
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>
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>
| Part | Description |
|---|---|
player | Outer player container |
controls | Controls row |
play-button | Play/pause button |
timeline | Seek range input |
volume | Volume range input |
track-info | Track title and time display |
| Event | Detail |
|---|---|
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);});
| Key | Action |
|---|---|
| Space | Play / Pause |
| Left Arrow | Seek back 10 seconds |
| Right Arrow | Seek forward 10 seconds |
| M | Toggle mute |
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>
<li> elements in .track-list use data attributes for state:
| Attribute | Set by | Meaning |
|---|---|---|
data-audio-active | Component + author | Currently loaded/playing track |
data-audio-played | Component | Track has been played this session |
data-audio-favorite | Author | Editorially marked as a highlight |
aria-label attributesrole="group" with labelprefers-reduced-motion<audio> without JSPair 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.
<audio> — Native audio element (Layers 1-3)<audio-visualizer> — Canvas visualization companion