Neu Music Player
A 300 px raised audio player: a 120 px round cover that spins like a record (one turn in 8 s) only while playing, a carved progress bar that moves, previous and next buttons that restart and notify your code. Drives an audio element if it finds one.
Updated
This effect is part of Effect.Labs — 840 vanilla effects, some free, some premium. Glassmorphism has 47 effects, including 6 free. Explore the category →
3 usage examples



How it works
The .neu-player case (300 px, padding: 24px, border-radius: 24px) rises from the #e0e5ec material through two 10 px / 20 px shadows. Inside, top to bottom: the .neu-player-art cover, a 120 px disc in a #8b5cf6 → #ec4899 gradient with a white music note, raised (6 px / 12 px); the title (15.2 px, #374151) and the artist; a carved 6 px progress groove (2 px / 4 px inset shadows) whose fill reuses the gradient; three round raised buttons, 40 px for previous and next, 52 px for play, which switch to inset shadows while pressed (:active).
The cover always carries the neuSpin animation (one turn in 8 s, linear, looping) but in animation-play-state: paused; the .playing class on the player switches it to running. The rotation therefore resumes where it stopped, like a record you let go. The JavaScript (a loop over every .neu-player on the page) wires the buttons: play toggles .playing and aria-pressed; without an <audio> element, progress moves 0.25 s every quarter second over a duration read from data-duration (180 s by default) and restarts at the end. Previous and next reset the progress to the start, then dispatch neuplayer:prev or neuplayer:next on the player: that is where your code changes track. If the player contains an <audio>, the script plays and pauses it, ties the bar to currentTime / duration and dispatches neuplayer:next when the track ends (measured in Chromium and Firefox with a 4 s file).
The player ships with its script, and with no inline onclick: a mere attribute on the play button would spin the cover, but the bar would stay frozen and previous/next would do nothing (test: 88.2 px before and after 1.2 s of playback). The script moves the bar and fires events your code can listen to, and it stays compatible with a security policy that forbids inline scripts. The three buttons only hold an icon: they therefore carry type="button", an aria-label (“Morceau précédent”, “Lecture”, “Morceau suivant”) and a violet focus ring. .neu-player-scene carries its surface (#e0e5ec, padding: 40px) like the whole family.
Accessibility
- Named buttons and keyboard: measured in Chromium and Firefox, Tab goes through previous, play, next; Enter and Space activate them; the play button announces its state through
aria-pressed(“Lecture, button, pressed”). Focus is shown byoutline: 2px solid #8b5cf6(3.3:1). The play icon itself does not turn into “pause”: onlyaria-pressedand the spinning cover say the music is playing — see Customize to swap the icon. - prefers-reduced-motion not in the code: the cover spins continuously during playback, even when the system asks for less motion. Add
@media (prefers-reduced-motion: reduce) { .neu-player-art { animation: none } }: playback is still signaled byaria-pressedand the moving bar. - Contrast: the
#374151title measures 8.1:1 on the material, but the#9ca3afartist (12.5 px) only 2.0:1 — switch it to#4b5563(6.0:1), as in the scenes. The gray#6b7280icons (3.8:1) and the violet one (3.3:1) pass the 3:1 threshold for graphics; the buttons' outlines only exist through shadows (1.5:1). - Progress bar: it is a
divwith no role, not operable. For a real player, addrole="progressbar"witharia-valuenow, or replace it with aninput type="range"if users must be able to seek (see Neu Slider, fx-0315).
Browser compatibility
CSS: drop and inset box-shadow, @keyframes on transform: rotate(), animation-play-state, :focus-visible. JavaScript: CustomEvent (Chrome 15, Firefox 11, Safari 6), optional HTMLMediaElement. Measured in Chromium, Firefox and WebKit (Playwright): spinning only while playing, progress moving, previous/next resetting and dispatching their event, no console error. audio.play() is only allowed after a user gesture: that is the case here, since playback starts from a click.
The versions given are those of :focus-visible; before them, everything works except the keyboard focus ring. Without JavaScript, the player shows but no longer reacts: still cover, buttons without effect.
The code
HTML structure to paste into your page (the CSS + JS come with Premium):
<div class="neu-player-scene">
<div class="neu-player">
<!-- optional: data-duration="180" on .neu-player (length in seconds), or <audio src="…"></audio> -->
<div class="neu-player-art"><svg viewBox="0 0 24 24" aria-hidden="true">…</svg></div>
<div class="neu-player-info">
<h4>Midnight Drive</h4>
<p>Synthwave FM</p>
</div>
<div class="neu-player-progress"><div class="neu-player-progress-fill"></div></div>
<div class="neu-player-controls">
<button type="button" class="neu-player-btn neu-player-btn--prev" aria-label="Morceau précédent">…</button>
<button type="button" class="neu-player-btn neu-player-btn--play" aria-label="Lecture" aria-pressed="false">…</button>
<button type="button" class="neu-player-btn neu-player-btn--next" aria-label="Morceau suivant">…</button>
</div>
</div>
</div>
The full HTML + CSS + JS of Neu Music Player, ready to paste — plus all 840 effects. Or $299 once, lifetime access.
Customize
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
Plug in real sound (HTML — <audio src="…"> inside .neu-player) |
no audio: progress simulated over data-duration | Place an <audio src="track.mp3" preload="metadata"></audio> anywhere in the player: play, pause, progress and end of track follow with no other code. |
Change track (JS — neuplayer:prev / neuplayer:next events) |
back to the start of the track, nothing else | player.addEventListener('neuplayer:next', () => { audio.src = tracks[++i]; h4.textContent = titles[i]; audio.play(); }). The event bubbles: a single listener on document serves several players. |
Play / pause icon (HTML + CSS — .neu-player.playing) |
fixed play icon | Add a second SVG (two bars) in the button and swap them: .neu-player .ico-pause, .neu-player.playing .ico-play { display: none } .neu-player.playing .ico-pause { display: block }. |
Spin speed (CSS — animation: 8s linear … neuSpin) |
one turn in 8 s | 1.8 s is the real speed of a 33 rpm record; beyond 12 s, the rotation goes unnoticed. |
Cover (CSS — .neu-player-art, #8b5cf6 → #ec4899 gradient) |
gradient disc with a note | Replace the background with the album art: background: url(cover.jpg) center / cover, and remove the SVG. Keep border-radius: 50% for the record effect. |
FAQ
<audio> element, it is the interface alone, with progress simulated over data-duration. Add an <audio> inside .neu-player and the script drives it: play and pause on the button, bar tied to the real position, neuplayer:next event at the end of the track.neuplayer:prev: your code loads the previous track if there is one.