Vertical Dual-Handle Range Slider — HTML CSS JS Snippet

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

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.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain why the pointer-to-value conversion inverts the percentage (1 - y / rect.height) for this vertical slider specifically, and what would visually break if that inversion were removed. It's also worth asking the assistant to add draggable numeric labels next to each handle that can be typed into directly, or to add a third "target" marker on the track (e.g. an ideal temperature) that stays independent of the draggable min/max handles.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a vertical dual-handle range slider in plain HTML, CSS, and vanilla JavaScript with two independently draggable handles on a vertical track that can never cross each other — no library.

Requirements:
- A vertical track element with two separate handle elements (a minimum and a maximum), each capable of being dragged along the track independently, plus a visible fill segment that spans exactly the space between the two handles' current positions.
- All position and value calculations must correctly account for the vertical orientation reading bottom-to-top (the bottom of the track represents the minimum value, the top represents the maximum), converting a pointer's vertical screen coordinate into a value using an inverted percentage calculation appropriate for that orientation.
- The maximum handle must be prevented from ever being dragged to a value at or below the minimum handle's current value, and the minimum handle must be prevented from ever exceeding the maximum handle's current value — enforced live during dragging, not only on release.
- Dragging must work via pointer events with a touch event fallback, and the drag must not trigger native page scrolling on touch devices while active.
- A live numeric readout displaying both the current minimum and maximum values, updating in real time as either handle moves.
- Keyboard support: each handle must be independently focusable and respond to Up/Down arrow keys to adjust its value by a small step (respecting the same never-cross constraint as dragging), with a modifier key (e.g. Shift) producing a larger step size.
- Both handles must carry correct ARIA slider role and value attributes reflecting their current state.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

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>

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

Temperature or thermostat range setting
The exact pattern this snippet models — a natural vertical metaphor for hot/cold ranges.
Audio equalizer or level controls
Vertical dual-handle sliders map naturally onto min/max gain or frequency band ranges.
Data dashboard threshold ranges
Set acceptable min/max thresholds for an alert or monitoring range on a vertical gauge.
Price or capacity range filters in a sidebar
A vertical range slider fits naturally into a narrow filter sidebar where horizontal space is tight.

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.