| .gitignore | Init | |
| audio-elem-wrapper.js | Init | |
| audio-elem-wrapper.min.js | Init | |
| CHANGELOG.md | Init | |
| LICENSE | Init | |
| package.json | Init | |
| README.md | Init | |
| screenshot.webp | Init | |
audio-elem-wrapper
A lightweight, zero-dependency audio player UI for native <audio> elements. Drop in a single script tag — no build step, no dependencies, no configuration required.
Vanilla JS. 3.3kB minified.
Why
The native <audio> element has very limited styling possibilities — its appearance is controlled by the browser, resulting in a different look on every platform. audio-elem-wrapper replaces the native controls with a consistent, fully styleable UI while keeping the native <audio> element underneath doing what it does best.
Features
- Replaces the native browser audio controls with a clean, consistent UI
- Built-in dark and light themes
- Fully accessible — keyboard navigable, screen reader friendly
- Completely customisable via CSS custom properties
- Self-contained — styles are injected automatically, no separate CSS file needed
Usage
Include the script and add data-audio-elem-wrapper to any <audio> element:
<script src="audio-elem-wrapper.min.js"></script>
<audio data-audio-elem-wrapper src="track.mp3" preload="metadata"></audio>
Multiple source formats with fallback are fully supported:
<audio data-audio-elem-wrapper preload="metadata">
<source src="track.flac" type="audio/flac" />
<source src="track.ogg" type="audio/ogg" />
<source src="track.mp3" type="audio/mpeg" />
</audio>
The script can be placed anywhere in the document — in <head> or end of <body>.
Attributes
| Attribute | Value | Description |
|---|---|---|
data-audio-elem-wrapper |
— | Activates the player on this element |
theme |
"dark" | "light" |
Sets the colour theme. Defaults to dark |
data-label |
string | Label announced by screen readers. Defaults to "Audio player" |
<audio data-audio-elem-wrapper theme="light" data-label="MF Reverb demo" src="track.mp3"></audio>
Customisation
All visual properties are exposed as CSS custom properties on .audio-elem-wrapper. Override them from your own stylesheet:
.audio-elem-wrapper {
--aew-accent: hotpink;
}
Or scope overrides to a specific instance via a parent element:
.my-card .audio-elem-wrapper {
--aew-accent: hotpink;
}
CSS custom properties
Colors
| Property | Default (dark) | Description |
|---|---|---|
--aew-bg |
#1d1d1d |
Player background |
--aew-text |
#ddd |
Text and icon color |
--aew-track-empty |
#444 |
Unfilled portion of seek/volume bars |
--aew-accent |
#3aa0ff |
Filled portion of seek/volume bars and focus ring |
--aew-accent-muted |
#888 |
Volume bar color when muted |
--aew-mute-color |
#db4848 |
Mute indicator (X) color |
Layout
| Property | Default | Description |
|---|---|---|
--aew-radius |
6px |
Player border radius |
--aew-padding |
6px 12px |
Player inner padding |
--aew-gap |
10px |
Spacing between controls |
--aew-font-size |
16px |
Base font size |
Controls
| Property | Default | Description |
|---|---|---|
--aew-track-height |
6px |
Height of seek/volume bars |
--aew-thumb-size |
16px |
Size of the draggable thumb |
--aew-vol-width |
80px |
Width of the volume slider |
Theme switching
The theme attribute is reactive — updating it at runtime will immediately re-theme the player. This makes it easy to wire up to a page-level dark/light toggle:
document.querySelectorAll('audio[data-audio-elem-wrapper]').forEach(a => {
a.setAttribute('theme', isDark ? 'dark' : 'light');
});
Accessibility
- All controls are keyboard accessible
- Play/pause and mute buttons have dynamic
aria-labelvalues that update with state - Seek bar announces current position as
"1:23 of 3:45"viaaria-valuetext - Player is grouped with
role="group"and labelled viadata-label - Focus indicators use
--aew-accentcolor via:focus-visible - The underlying
<audio>element is hidden from assistive technology (aria-hidden="true") to avoid duplicate controls
Browser support
All modern browsers. Requires support for:
- CSS custom properties
appearance: noneon range inputs:focus-visible
Minification
For production use, the file can be significantly reduced in size with a two-pass approach:
-
Terser — standard JS minification (remove whitespace, shorten variable names, etc.) https://try.terser.org
-
JSCrush — compresses the output further by exploiting string repetition patterns http://www.iteral.com/jscrush
This brings audio-elem-wrapper.js from 8.8kB → 3.3kB with no loss of functionality.
License
MIT