Mobile Music Player Screen — Free HTML CSS JS UI
Mobile Music Player Screen · Mobile · Plain HTML, CSS & JS · Live preview
What's included
Features
About this UI Snippet
Mobile Music Player Screen — Now Playing UI

A now-playing screen is the emotional center of a music app — big album art, the track title, a scrubbable progress bar, and a ring of transport controls. This snippet builds a complete, functional one inside a CSS phone frame: the play button starts a real elapsed-time clock, the artwork disc spins only while playing, and the seek bar is draggable with the pointer — in HTML, CSS, and vanilla JavaScript with no dependency or audio file.
A play state that drives everything
Tapping play sets a playing flag, swaps the button glyph to a pause bar, and starts a setInterval that increments the current time every second and re-renders the progress bar. Pausing clears the interval and restores the play glyph. A single render() function converts the elapsed seconds into a percentage for the fill width and knob position and formats the m:ss time, so the visual bar and the clock can never disagree.
The spinning record, tied to playback
The album art holds a small vinyl disc drawn entirely in CSS with a repeating-radial-gradient for the grooves and a pink center label. It has a continuous rotation animation that is paused by default; adding a .playing class flips animation-play-state to running, so the record spins only while the track plays and freezes the instant you pause — a small, satisfying detail that needs no JavaScript beyond the class toggle.
A draggable seek bar
The progress track responds to both a click-to-seek and a drag on the knob using Pointer Events. Clicking anywhere jumps the time to that fraction; pressing the knob starts a drag that follows pointermove on the window (so the finger can leave the bar) and releases on pointerup. The seek math clamps the ratio to 0..1 so you can never scrub past either end, and it reads clientX from touch or mouse events alike.
Transport toggles
Like, shuffle, and repeat are independent toggle buttons that flip an accent color, mirroring how these persistent modes work in real players versus the momentary previous/next buttons. The like heart also swaps between outline and filled.
Accessibility and performance
Every transport control is a real <button> with an aria-label that updates between "Play" and "Pause" as the state changes, so screen readers always announce the current action. When you adapt this for production, the seek bar should become a proper role="slider" with aria-valuenow, aria-valuemin, and aria-valuemax plus arrow-key handling so it is operable without a pointer — the visual track is already in place to style over. Performance is careful about the two moving parts: the vinyl spin is a pure CSS animation whose play state is toggled by a class, so it never touches JavaScript per frame, and the seek drag reads clientX and writes two style values without triggering layout in a loop. The one-second interval that advances the clock is the only timer, and it does a single percentage calculation per tick. Swapping the interval for an <audio> element's timeupdate event removes even that, letting the browser drive both the audio and the progress bar from one source.
Reusing it
Point the play/pause and seek at a real <audio> element — bind currentTime, duration, and the timeupdate event instead of the interval — and feed the art and metadata from your track data. Lift it out of the phone frame for a responsive web player, or keep it framed beside a music player mini-bar to present a full listening experience.
Step by step
How to Use
- 1Paste HTML, CSS, and JSA full-screen now-playing view renders with album art and transport controls.
- 2Press playThe elapsed time starts counting, the progress bar fills, and the vinyl disc begins spinning.
- 3Pause itThe clock stops, the button returns to a play triangle, and the disc freezes in place.
- 4Scrub the trackClick anywhere on the seek bar to jump, or drag the knob to scrub to any position.
- 5Like the songThe heart toggles between outline and a filled pink state.
- 6Toggle shuffle and repeatEach mode button flips its accent color independently.
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
In the demo it is driven by a setInterval that increments a counter each second and loops at the track length. To play real audio, create an audio element, start it on play, and update the bar from its timeupdate event using currentTime and duration instead of the interval. The render function stays the same — only the time source changes.
The disc has a continuous rotation keyframe that is set to animation-play-state: paused by default. Playing adds a .playing class that switches it to running, and pausing removes the class. Because animation-play-state freezes and resumes without resetting, the record picks up exactly where it stopped.
The track listens for a click to seek and uses Pointer Events for dragging: pressing the knob sets a dragging flag, pointermove on the window updates the position so your finger can leave the bar, and pointerup ends the drag. The x offset is converted to a 0-to-1 ratio, clamped so you cannot scrub past either end, and mapped to the current time.
Shuffle and repeat are persistent modes, so they are toggle buttons that keep an on state and accent color. Previous and next are momentary actions that fire once, so they are plain buttons. This mirrors how real players distinguish sticky modes from one-shot skips.
Hold playing, currentTime, and the toggle states in state, and drive them from a real audio ref. Start and stop it in a handler, and update currentTime from the audio timeupdate event in a useEffect (React), watcher (Vue), or ngZone callback (Angular). Bind the fill width and playing class to state rather than mutating the DOM. Tailwind expresses the gradients and controls with utilities.