webdevpuneet.com

Price Range Slider — Free HTML CSS JS Dual Handle Range Slider Snippet

Price Range Slider · Forms · Plain HTML, CSS & JS · Live preview

CategoryForms

Copy & paste

Source Code

<div class="range-widget">
  <div class="range-labels">
    <span>Price Range</span>
    <span class="range-value" id="rangeValue">$20 - $180</span>
  </div>
  <div class="range-track-wrap">
    <div class="range-track"></div>
    <div class="range-fill" id="rangeFill"></div>
    <input type="range" min="0" max="200" value="20" step="1" class="range-input range-min" id="minRange" aria-label="Minimum price">
    <input type="range" min="0" max="200" value="180" step="1" class="range-input range-max" id="maxRange" aria-label="Maximum price">
  </div>
</div>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month), 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 overlapping native range inputs simulate a single dual-handle slider
pointer-events trick makes only the visible thumbs interactive, not the invisible tracks
Colored fill bar recalculated live as a percentage of the slider range on every input event
Handles are prevented from crossing via a configurable minimum GAP constant
Live "$X - $Y" label updates on every drag, not just on release
Full native keyboard support (arrow keys, Home/End, Page Up/Down) inherited from <input type="range">
Custom-styled circular thumbs with a focus-visible outline for accessibility
No slider plugin or library — pure HTML inputs plus about 25 lines of JavaScript

About this UI Snippet

Price Range Slider — HTML, CSS & JavaScript Dual-Thumb Range Filter

Screenshot of the Price Range Slider snippet rendered live

Filtering products by a min/max price range is a staple of e-commerce filter sidebars. Building it usually means reaching for a slider plugin — but a convincing dual-handle range slider can be built with two overlapping native <input type="range"> elements and a small amount of JavaScript to keep them from crossing.

How two range inputs simulate one dual-handle slider

Both #minRange and #maxRange are absolutely positioned on top of each other, spanning the same track. Each is styled with a transparent background and pointer-events: none on the input track itself, but the ::-webkit-slider-thumb/::-moz-range-thumb pseudo-elements get pointer-events: auto. This means clicking anywhere on the invisible track does nothing, but clicking and dragging either visible circular thumb works normally — the two inputs never fight over the same click because only their thumbs are interactive, and the thumbs sit at different positions.

How the colored fill bar is calculated

A separate .range-fill div (not part of either input) is positioned absolutely between the two thumbs. updateSlider() converts both values to percentages of the slider's max ((value / MAX) * 100) and sets the fill's left to the min percentage and width to the difference between max and min percentages. This is recalculated on every input event from either slider, so the fill visually tracks both thumbs in real time.

How the handles are prevented from crossing

Nothing stops a native range input from having its value larger than a sibling's on its own — the two inputs know nothing about each other. updateSlider checks whether maxVal - minVal has dropped below a minimum GAP, and if so, it forces the *other* slider's value back to maintain that gap, using this (the slider that just fired the event) to determine which one to leave alone and which one to push. This keeps a small buffer between the handles so they never visually overlap or invert.

Why two separate inputs instead of a single custom-built slider

Reusing native <input type="range"> elements means keyboard support (arrow keys, Page Up/Down, Home/End), touch dragging, and screen reader value announcements all come for free from the browser — a fully custom-built slider would need to reimplement all of that manually.

Step by step

How to Use

  1. 1
    Load the snippetClick "Price Range Slider" in the sidebar Library tab to load the dual-handle slider.
  2. 2
    Drag both handlesDrag the left and right thumbs in the preview and watch the fill bar and "$X - $Y" label update live.
  3. 3
    Test the minimum gapTry dragging one handle into the other — notice it stops before crossing, maintaining a small gap.
  4. 4
    Change the price rangeUpdate the min/max attributes on both range inputs and the MAX constant in JS to match your real price bounds.
  5. 5
    Adjust the minimum gapChange the GAP constant in the JS panel to allow the handles to get closer or further apart.
  6. 6
    Export and saveExport as HTML/JSX/Tailwind or click "Save as" to reuse this filter in a real product listing page.

Real-world uses

Common Use Cases

SHOP
E-commerce price filters
Let shoppers narrow a product listing to a specific price band without typing numbers into two separate fields.
Booking and rental date/price filters
Reuse the same dual-handle pattern for filtering by budget, distance, or duration ranges.
Analytics dashboard range filters
Apply the pattern to filter a chart or table by a numeric range like age, score, or duration.
Learn the overlapping-inputs technique
Study how pointer-events selectively enables only the thumb pseudo-elements while leaving the track inert.
Accessible range controls
See how reusing native range inputs preserves keyboard and screen-reader support that a from-scratch slider would need to reimplement.
Related: Web Crypto Hash Demo
See the Web Crypto Hash Demo for a related forms pattern worth pairing with this one.

Got questions?

Frequently Asked Questions

Both inputs are absolutely positioned on top of the same track with transparent backgrounds. Only their thumb pseudo-elements have pointer-events enabled, so users can drag either thumb independently while the invisible track underneath ignores clicks.

The updateSlider function checks the gap between the current min and max values after every input event. If the gap falls below a configured minimum, it pushes the slider that did not just move back to restore that minimum gap, preventing the handles from passing through each other.

Both slider values are converted to percentages of the maximum range value. The fill's left offset is set to the minimum percentage and its width is set to the difference between the max and min percentages, so it always spans exactly between the two visible thumbs.

Native range inputs come with built-in keyboard support, touch dragging, and screen reader value announcements for free. A fully custom slider built from plain divs would need to reimplement all of that accessibility and interaction behavior manually.

Update the min and max attributes on both #minRange and #maxRange inputs in the HTML, and update the matching MAX constant in the JavaScript panel so the fill-bar percentage math stays correct.

Yes — change the string concatenation in updateSlider's valueDisplay.textContent line to use your currency symbol or a proper Intl.NumberFormat currency formatter.

Yes — native range inputs have built-in touch drag support in all modern mobile browsers, so the thumbs can be dragged with a finger exactly as they are with a mouse.