More Forms Snippets
Volume Control — Free Custom Slider HTML CSS JS Snippet
Volume Control · Forms · Plain HTML, CSS & JS · Live preview
What's included
Features
About this UI Snippet
Volume Control — Custom Draggable Volume Slider

A volume control is the speaker-plus-slider you find in every media player, video app, and sound settings panel. This snippet builds a polished, fully custom one — not a styled <input type=range> — in HTML, CSS, and vanilla JavaScript, with pointer dragging, a mute button that remembers the last level, full keyboard support, and a speaker icon that changes with the volume. No library, no audio dependency.
A custom track, not a native range
The track is a plain div with an absolutely-positioned fill and a round knob whose left is a percentage. Building it by hand (instead of restyling <input type=range>, which is notoriously inconsistent across browsers) gives total control over the knob size, the focus ring, and the active scale animation. touch-action:none on the track stops the browser from scrolling the page while you drag on touch devices.
Pointer Events for unified drag
A single set of Pointer Events handles mouse, touch, and pen identically. On pointerdown the track calls setPointerCapture, so dragging keeps tracking even when the cursor leaves the element, and fromPointer() converts clientX into a 0–100 value using getBoundingClientRect(). That one geometry function powers both the initial click-to-seek and continuous dragging.
Mute that remembers
The speaker button toggles muted, which renders the fill at zero without losing the real volume. A lastVolume variable stores the level so un-muting restores exactly where you were — and dragging the slider above zero auto-un-mutes, matching how native players behave. The icon swaps between muted, low, and high SVGs based on the current state, so the glyph always reflects what you'll hear.
Accessible slider semantics
The track uses role="slider" with aria-valuemin, aria-valuemax, and a live aria-valuenow, and is focusable via tabindex="0". Arrow keys nudge by 5, Home and End jump to mute and max, and the focus-visible ring plus a scaled knob make the keyboard state obvious — so it's operable and announced correctly without a mouse.
Wiring to audio
Point setVolume() at a real sink: set audioElement.volume = shown / 100 (the HTML5 media API expects 0–1) or a Web Audio GainNode.gain.value. Because every change funnels through setVolume() and paint(), syncing the UI to actual playback — including external volume changes — is a one-line addition.
Step by step
How to Use
- 1Paste HTML, CSS, and JSA speaker icon, a track, and a percentage render in a row.
- 2Drag the knobPointer dragging sets the volume and the fill follows your finger or cursor.
- 3Click anywhere on the trackThe knob jumps to that point — click-to-seek and drag share one function.
- 4Press the speaker to muteThe fill drops to zero but your level is remembered for un-muting.
- 5Use the keyboardFocus the track and press arrows to nudge, Home and End to jump.
- 6Connect to audioSet audio.volume = value / 100 inside setVolume().
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
Native range inputs are hard to style consistently — the track, thumb, and focus ring differ across browsers and need vendor pseudo-elements. A custom div track gives full control over the knob, the active scale, and the focus ring, while role=slider plus aria-valuenow restores the accessibility you'd otherwise get for free.
It uses Pointer Events, which unify mouse, touch, and pen. On pointerdown the track calls setPointerCapture so it keeps receiving moves even if the pointer leaves, and fromPointer() turns clientX into a 0–100 value using getBoundingClientRect(). touch-action:none prevents the page from scrolling mid-drag.
No. Muting sets a flag that renders the fill at zero but leaves the real volume untouched, and a lastVolume variable stores your level so un-muting returns to exactly where you were. Dragging above zero also auto-un-mutes, matching native media players.
Yes. The track is focusable and listens for keys: ArrowRight/Up and ArrowLeft/Down nudge by 5, Home mutes to 0, and End jumps to 100. A focus-visible ring and a slightly enlarged knob make the focused state clear, and aria-valuenow updates so screen readers announce the level.
Hold volume and muted in state and bind the fill width and knob left to the shown value. Attach the pointer handlers to a ref and set audio.volume = value / 100 in the setter. Put nothing in a global; the logic is self-contained. In Tailwind, position the fill and knob with inline styles for the percentage and use utilities for the rest.