webdevpuneet.com

Vertical Dual-Handle Range Slider — HTML CSS JS Snippet

Vertical Dual-Handle Range Slider · Forms · Plain HTML, CSS & JS · Live preview

CategoryForms

Copy & paste

Source Code

<div class="vdr-wrap">
  <div class="vdr-readout">
    <span id="vdrMax">78°</span>
    <span class="vdr-dash">—</span>
    <span id="vdrMin">62°</span>
  </div>
  <div class="vdr-track" id="vdrTrack">
    <div class="vdr-fill" id="vdrFill"></div>
    <div class="vdr-handle" id="vdrHandleMax" tabindex="0" role="slider" aria-label="Maximum temperature" aria-valuemin="50" aria-valuemax="90" aria-valuenow="78"></div>
    <div class="vdr-handle" id="vdrHandleMin" tabindex="0" role="slider" aria-label="Minimum temperature" aria-valuemin="50" aria-valuemax="90" aria-valuenow="62"></div>
  </div>
  <div class="vdr-scale"><span>90°</span><span>70°</span><span>50°</span></div>
</div>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month, 7 days free), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

What's included

Features

Two fully independent handles that mutually clamp — they can approach but never cross each other
Vertical orientation with position math inverted correctly (top of track = high value)
A single fill bar recalculated every render to span exactly the current min-to-max segment
Live numeric readout above the track, updating as either handle is dragged
Full keyboard support per handle — Up/Down for fine steps, Shift+Up/Down for coarse 5-unit steps
Unified pointer and touch dragging, with touch-action: none preventing scroll interference

About this UI Snippet

Vertical Dual-Handle Range Slider — Two Handles, Bottom-to-Top, Never Crossing

Screenshot of the Vertical Dual-Handle Range Slider snippet rendered live

Most range sliders run horizontally. This one runs vertically — the natural orientation for a thermostat, an equalizer band, or a volume-style range — with *two* independent handles instead of one, each holding its own value, and a fill bar that only ever spans the segment between them.

Position measured from the bottom, not the top

Because a vertical track visually reads bottom = low, top = high (the opposite of how a horizontal track reads left = low), every calculation here works in terms of bottom offsets rather than top. Converting a pointer's clientY into a value inverts the fraction: pct = 1 - y / rect.height, so a cursor near the top of the track produces a *high* percentage, matching the visual expectation.

Handles that physically cannot swap positions

Dragging the "max" handle clamps it with Math.max(valMin + 1, ...) — it can never be dragged below the min handle's value plus one whole degree — and dragging the "min" handle clamps the opposite way against the max handle. That mutual clamp is what makes the two handles behave like a genuine range rather than two independent sliders that happen to share a track: they can slide arbitrarily close together, but never cross or overlap in value.

One fill bar spanning exactly the selected range

The fill element's top and bottom offsets are both recalculated from the two handle percentages on every render — so the colored segment always represents precisely "everything between min and max," growing or shrinking as either handle moves, never needing to be manually kept in sync.

Step by step

How to Use

  1. 1
    Paste HTML, CSS, and JSA vertical track appears with two handles set to 62° and 78°, and a gradient fill between them.
  2. 2
    Drag either handleIt moves along the track and clamps against the other handle — they can't cross past each other.
  3. 3
    Watch the readoutThe numbers above the track update live as you drag either handle.
  4. 4
    Use the keyboardFocus a handle and press Up/Down to adjust by 1°, or hold Shift for 5° steps.
  5. 5
    Adjust the rangeChange the MIN/MAX constants in the JS to fit a different scale entirely.

Real-world uses

Common Use Cases

🌡️
Thermostat range settings
Set a minimum and maximum temperature on a vertical track that reads bottom to top, with two handles that can approach but never cross.
🎚️
Equalizer and level controls
Build vertical dual-handle bands for audio, with position maths inverted so the top of the track is the high value.
📊
Dashboard threshold ranges
Set acceptable minimum and maximum limits, with a single fill bar recalculated on every render to span exactly the chosen range.
💰
Sidebar range filters
Offer a vertical price or capacity filter, with a live numeric readout above the track as either handle moves.

Got questions?

Frequently Asked Questions

Change the MIN and MAX constants (and update the aria-valuemin/max attributes and the visible scale labels to match) — all position math derives from these two numbers.

Each handle's drag/keyboard update clamps against the other handle's current value plus or minus one unit — this is deliberate, since a "range" where min exceeds max has no meaningful interpretation for most real use cases like temperature or price ranges.

Swap bottom/top for left/right throughout the CSS and JS, and change the position calculation from clientY/rect.height to clientX/rect.width without inverting the percentage (left = low is already the natural horizontal reading).

Yes — valMax and valMin are plain JavaScript variables updated on every interaction; hook a callback or custom event dispatch inside render() to react to changes in a real application.

Both handles carry role="slider" with proper aria-valuemin/max/now and a descriptive aria-label, and are independently focusable and operable via Up/Down (and Shift+Up/Down for larger steps) without a mouse.