Volume Control — Free Custom Slider HTML CSS JS Snippet

Volume Control · Forms · Plain HTML, CSS & JS · Live preview

Share & Support

What's included

Features

Fully custom track
Hand-built div slider, not a restyled input range.
Pointer Events
One code path for mouse, touch, and pen with pointer capture.
Click-to-seek
Tapping the track jumps the knob via shared geometry math.
Mute with memory
Un-muting restores the exact prior level.
Dynamic speaker icon
Muted, low, and high glyphs follow the volume.
Keyboard control
Arrows, Home, and End with a focus-visible ring.
ARIA slider
role=slider with live aria-valuenow for screen readers.
No dependency
Pure HTML/CSS/JS — drop it onto any player.

About this UI Snippet

Volume Control — Custom Draggable Volume Slider

Screenshot of the Volume Control snippet rendered live

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

  1. 1
    Paste HTML, CSS, and JSA speaker icon, a track, and a percentage render in a row.
  2. 2
    Drag the knobPointer dragging sets the volume and the fill follows your finger or cursor.
  3. 3
    Click anywhere on the trackThe knob jumps to that point — click-to-seek and drag share one function.
  4. 4
    Press the speaker to muteThe fill drops to zero but your level is remembered for un-muting.
  5. 5
    Use the keyboardFocus the track and press arrows to nudge, Home and End to jump.
  6. 6
    Connect to audioSet audio.volume = value / 100 inside setVolume().

Real-world uses

Common Use Cases

Media players
Control playback level next to a video player.
Audio and music apps
Pair with a music player transport bar.
Voice and recording UIs
Set monitor level beside an audio waveform visualizer.
Settings panels
Drop into a settings panel for sound preferences.
Any value slider
Reuse the drag math for a range slider.
Learning Pointer Events
A reference for capture-based dragging and ARIA sliders.

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.