Bootstrap Price Range Slider — Free HTML CSS JS Snippet

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

What's included

Features

Two native <input type="range"> elements stacked precisely to fake a single dual-thumb slider
pointer-events: none on the full track with pointer-events: auto re-enabled only on the thumb, per browser engine
Dynamic z-index swapping (syncStacking) so neither thumb becomes unreachable when the two are close together
Independent min/max clamping with a MIN_GAP so the two handles can never fully overlap
A separate filled div computed from both values' percentages, since neither input can render its own segment
Live "$X – $Y" label recomputed from both current values on every input event
Cross-browser thumb styling via both: :-webkit-slider-thumb and ::-moz-range-thumb
Fully native keyboard support inherited for free from the underlying range inputs (arrow keys move the focused thumb)

About this UI Snippet

Bootstrap Price Range Slider — HTML, CSS & JavaScript

Screenshot of the Bootstrap Price Range Slider snippet rendered live

A dual-thumb slider is not a single form control in HTML — there is no native two-handle range input — so this snippet builds one from two ordinary <input type="range"> elements, #bsrsMin and #bsrsMax, stacked exactly on top of each other with position: absolute, top: 0, left: 0, width: 100% inside a shared .bsrs-track-wrap container. Both inputs share the same min, max, and step, so their thumbs travel the identical horizontal distance and stay pixel-aligned with each other and with the visual track underneath.

The trickiest CSS problem with two overlapping range inputs is that each one's own invisible hit-area normally spans its *entire* track width, so the top input would swallow every click across the whole slider and the bottom one would be completely unreachable. This snippet solves it the standard way: pointer-events: none is set on both .bsrs-range elements at the container level, disabling click/drag everywhere by default, and then re-enabled specifically on ::-webkit-slider-thumb and ::-moz-range-thumb with pointer-events: auto — so only the small circular thumb itself is interactive, not the invisible full-width track behind it. That alone still leaves an edge case: when the two thumbs are dragged close together, whichever input is stacked on top intercepts clicks meant for the one underneath. syncStacking() handles this by comparing the min thumb's position against the slider's midpoint and swapping zIndex between the two inputs accordingly, so whichever thumb is likely closer to being grabbed next stays on top.

Keeping the two values from crossing is handled independently in each input's own input listener: dragging the min thumb clamps its value to never exceed maxRange.value - MIN_GAP, and dragging the max thumb clamps it to never fall below minRange.value + MIN_GAP. MIN_GAP (set to 20) exists specifically so the two thumbs can never fully collide into the exact same pixel position, which would make it impossible to tell them apart or grab one specifically with a mouse.

updateFill() computes each thumb's value as a percentage of the full RANGE_MINRANGE_MAX span and applies that percentage as left and width on a separate #bsrsFill div sitting between the gray .bsrs-track and the two range inputs — this is what produces the visually filled blue segment between the two handles, since neither native range input can render a filled segment starting anywhere but its own left edge. The live "$X – $Y" label is rebuilt from the two current numeric values on every input event on either slider, so dragging either thumb updates both the fill and the label in the same frame.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Hand this snippet's HTML, CSS, and JS to an AI coding assistant like Claude and ask it to add editable number inputs next to the slider that stay in sync with the handles, or to add tick marks at common price breakpoints along the track. It's also worth asking for a non-linear scale (e.g. logarithmic) for a wider price range.

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 Bootstrap 5.3 dual-thumb price range slider using two native overlapping <input type="range"> elements, using the real Bootstrap CDN framework (bootstrap.min.css and bootstrap.bundle.min.js) for the surrounding card, not custom CSS made to resemble Bootstrap.

Requirements:
- Two native range inputs sharing identical min, max, and step values, absolutely positioned exactly on top of each other inside one container so their thumbs travel the same track.
- Disable pointer events on the full width of both inputs by default, and re-enable pointer events only on each input's thumb (both -webkit-slider-thumb and -moz-range-thumb), so the invisible full-width track of one input never blocks interaction with the other's thumb.
- Prevent the two handles from crossing: dragging the lower handle must never let its value exceed the upper handle's value minus a small fixed gap, and vice versa for the upper handle.
- When the two handles get close together, dynamically adjust which input's z-index is higher based on position, so neither handle becomes permanently unreachable by mouse.
- Render a separate filled bar between the two handle positions, computed from both values as percentages of the full range, since neither native input alone can render a segment that doesn't start at its own edge.
- Display a live "$X – $Y" label above the slider that updates immediately as either handle is dragged.

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="container py-5 d-flex justify-content-center">
  <div class="card bsrs-card">
    <div class="card-body p-4">
      <h5 class="fw-bold mb-1 text-center">Filter by price</h5>
      <p class="text-center fw-semibold mb-3" id="bsrsLabel">$0 &ndash; $1000</p>

      <div class="bsrs-track-wrap mb-2">
        <div class="bsrs-track"></div>
        <div class="bsrs-fill" id="bsrsFill"></div>
        <input type="range" min="0" max="1000" step="10" value="0" class="bsrs-range" id="bsrsMin">
        <input type="range" min="0" max="1000" step="10" value="1000" class="bsrs-range" id="bsrsMax">
      </div>

      <div class="d-flex justify-content-between small text-muted">
        <span>$0</span>
        <span>$1000</span>
      </div>
    </div>
  </div>
</div>

Step by step

How to Use

  1. 1
    Load the snippetA card appears with a "$0 – $1000" label and a track with two circular handles at each end.
  2. 2
    Drag the left handle to the rightThe blue filled segment shrinks from the left, and the label updates live to something like "$220 – $1000".
  3. 3
    Drag the right handle to the leftThe filled segment shrinks from the right instead, and the label's upper value updates in real time.
  4. 4
    Try dragging the min handle past the max handleIt stops short of crossing, always leaving a small gap, rather than overtaking or hiding behind the other handle.
  5. 5
    Drag both handles close together near the middleYou can still grab and move either handle individually — the stacking order adjusts automatically so neither thumb becomes unreachable.
  6. 6
    Release either handleThe label and the blue filled segment stay exactly in sync with the two handle positions at all times, not just on release.

Real-world uses

Common Use Cases

CART
E-commerce price filtering sidebars
Pair with bootstrap-offcanvas-cart and a product grid to let shoppers filter listings by a price range.
SEARCH
Marketplace and real estate search filters
A natural fit alongside bootstrap-responsive-navbar-search for filtering listings by budget.
Pricing and budget selection in signup or quote forms
Let users indicate a budget range as part of a quote request or plan-selection form, similar in spirit to bootstrap-pricing-table-toggle.
Admin dashboards filtering numeric ranges
Reuse the same dual-range pattern for filtering order totals, dates, or any bounded numeric range in an admin table.
Learning overlapping input hit-testing techniques
A focused example of the pointer-events layering trick needed whenever two form controls must share the same visual space.

Got questions?

Frequently Asked Questions

Native range inputs come with built-in keyboard support (arrow keys, Page Up/Down, Home/End), touch dragging, and accessibility semantics for free. Building a fully custom slider from scratch would mean reimplementing all of that manually, so overlapping two native inputs and layering the visuals is the more robust approach.

Yes. In React, hold minValue/maxValue in useState and compute the fill percentage and label in the render function instead of touching style properties directly; in Vue, use two v-model-bound refs with a computed fill style; in Angular, bind [(ngModel)] to two component properties and compute the fill width in the template or a getter.

Each input's own input event listener clamps its new value against the other input's current value minus or plus a fixed MIN_GAP (20 in this snippet) — the min handle can never be dragged past max minus the gap, and vice versa, so they never fully overlap or invert order.

Since both range inputs are visually stacked in the same space, whichever one has a higher stacking order intercepts pointer events across its full width even where invisible. syncStacking() raises whichever thumb is more likely to be the next target above the midpoint of the range, so both handles stay reachable even when dragged close together.

Keep the two native range inputs and all JS logic unchanged, then rebuild .bsrs-track and .bsrs-fill as Tailwind absolute inset-y-0 my-auto h-1 rounded-full divs with bg-gray-200 and bg-blue-600 respectively, and restyle the thumbs using Tailwind's arbitrary-value pseudo-element utilities or a small scoped style block for the vendor-prefixed thumb selectors.

Yes — native range inputs support touch dragging out of the box on mobile browsers, and because the pointer-events restriction only blocks the invisible track (not the thumb itself), tapping and dragging either visible handle works the same as with a mouse.