[forms] HTML5: Slider with two inputs possible?

Is it possible to make a HTML5 slider with two input values, for example to select a price range? If so, how can it be done?

This question is related to forms html slider

The answer is


Sure you can simply use two sliders overlaying each other and add a bit of javascript (actually not more than 5 lines) that the selectors are not exceeding the min/max values (like in @Garys) solution.

Attached you'll find a short snippet adapted from a current project including some CSS3 styling to show what you can do (webkit only). I also added some labels to display the selected values.

It uses JQuery but a vanillajs version is no magic though.

@Update: The code below was just a proof of concept. Due to many requests I've added a possible solution for Mozilla Firefox (without changing the original code). You may want to refractor the code below before using it.

_x000D_
_x000D_
    (function() {

        function addSeparator(nStr) {
            nStr += '';
            var x = nStr.split('.');
            var x1 = x[0];
            var x2 = x.length > 1 ? '.' + x[1] : '';
            var rgx = /(\d+)(\d{3})/;
            while (rgx.test(x1)) {
                x1 = x1.replace(rgx, '$1' + '.' + '$2');
            }
            return x1 + x2;
        }

        function rangeInputChangeEventHandler(e){
            var rangeGroup = $(this).attr('name'),
                minBtn = $(this).parent().children('.min'),
                maxBtn = $(this).parent().children('.max'),
                range_min = $(this).parent().children('.range_min'),
                range_max = $(this).parent().children('.range_max'),
                minVal = parseInt($(minBtn).val()),
                maxVal = parseInt($(maxBtn).val()),
                origin = $(this).context.className;

            if(origin === 'min' && minVal > maxVal-5){
                $(minBtn).val(maxVal-5);
            }
            var minVal = parseInt($(minBtn).val());
            $(range_min).html(addSeparator(minVal*1000) + ' €');


            if(origin === 'max' && maxVal-5 < minVal){
                $(maxBtn).val(5+ minVal);
            }
            var maxVal = parseInt($(maxBtn).val());
            $(range_max).html(addSeparator(maxVal*1000) + ' €');
        }

     $('input[type="range"]').on( 'input', rangeInputChangeEventHandler);
})();
_x000D_
body{
font-family: sans-serif;
font-size:14px;
}
input[type='range'] {
  width: 210px;
  height: 30px;
  overflow: hidden;
  cursor: pointer;
    outline: none;
}
input[type='range'],
input[type='range']::-webkit-slider-runnable-track,
input[type='range']::-webkit-slider-thumb {
  -webkit-appearance: none;
    background: none;
}
input[type='range']::-webkit-slider-runnable-track {
  width: 200px;
  height: 1px;
  background: #003D7C;
}

input[type='range']:nth-child(2)::-webkit-slider-runnable-track{
  background: none;
}

input[type='range']::-webkit-slider-thumb {
  position: relative;
  height: 15px;
  width: 15px;
  margin-top: -7px;
  background: #fff;
  border: 1px solid #003D7C;
  border-radius: 25px;
  z-index: 1;
}


input[type='range']:nth-child(1)::-webkit-slider-thumb{
  z-index: 2;
}

.rangeslider{
    position: relative;
    height: 60px;
    width: 210px;
    display: inline-block;
    margin-top: -5px;
    margin-left: 20px;
}
.rangeslider input{
    position: absolute;
}
.rangeslider{
    position: absolute;
}

.rangeslider span{
    position: absolute;
    margin-top: 30px;
    left: 0;
}

.rangeslider .right{
   position: relative;
   float: right;
   margin-right: -5px;
}


/* Proof of concept for Firefox */
@-moz-document url-prefix() {
  .rangeslider::before{
    content:'';
    width:100%;
    height:2px;
    background: #003D7C;
    display:block;
    position: relative;
    top:16px;
  }

  input[type='range']:nth-child(1){
    position:absolute;
    top:35px !important;
    overflow:visible !important;
    height:0;
  }

  input[type='range']:nth-child(2){
    position:absolute;
    top:35px !important;
    overflow:visible !important;
    height:0;
  }
input[type='range']::-moz-range-thumb {
  position: relative;
  height: 15px;
  width: 15px;
  margin-top: -7px;
  background: #fff;
  border: 1px solid #003D7C;
  border-radius: 25px;
  z-index: 1;
}

  input[type='range']:nth-child(1)::-moz-range-thumb {
      transform: translateY(-20px);    
  }
  input[type='range']:nth-child(2)::-moz-range-thumb {
      transform: translateY(-20px);    
  }
}
_x000D_
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
<div class="rangeslider">
                                <input class="min" name="range_1" type="range" min="1" max="100" value="10" />
                                <input class="max" name="range_1" type="range" min="1" max="100" value="90" />
                                <span class="range_min light left">10.000 €</span>
                                <span class="range_max light right">90.000 €</span>
                            </div>
_x000D_
_x000D_
_x000D_


Actually I used my script in html directly. But in javascript when you add oninput event listener for this event it gives the data automatically.You just need to assign the value as per your requirement.

_x000D_
_x000D_
[slider] {_x000D_
  width: 300px;_x000D_
  position: relative;_x000D_
  height: 5px;_x000D_
  margin: 45px 0 10px 0;_x000D_
}_x000D_
_x000D_
[slider] > div {_x000D_
  position: absolute;_x000D_
  left: 13px;_x000D_
  right: 15px;_x000D_
  height: 5px;_x000D_
}_x000D_
[slider] > div > [inverse-left] {_x000D_
  position: absolute;_x000D_
  left: 0;_x000D_
  height: 5px;_x000D_
  border-radius: 10px;_x000D_
  background-color: #CCC;_x000D_
  margin: 0 7px;_x000D_
}_x000D_
_x000D_
[slider] > div > [inverse-right] {_x000D_
  position: absolute;_x000D_
  right: 0;_x000D_
  height: 5px;_x000D_
  border-radius: 10px;_x000D_
  background-color: #CCC;_x000D_
  margin: 0 7px;_x000D_
}_x000D_
_x000D_
_x000D_
[slider] > div > [range] {_x000D_
  position: absolute;_x000D_
  left: 0;_x000D_
  height: 5px;_x000D_
  border-radius: 14px;_x000D_
  background-color: #d02128;_x000D_
}_x000D_
_x000D_
[slider] > div > [thumb] {_x000D_
  position: absolute;_x000D_
  top: -7px;_x000D_
  z-index: 2;_x000D_
  height: 20px;_x000D_
  width: 20px;_x000D_
  text-align: left;_x000D_
  margin-left: -11px;_x000D_
  cursor: pointer;_x000D_
  box-shadow: 0 3px 8px rgba(0, 0, 0, 0.4);_x000D_
  background-color: #FFF;_x000D_
  border-radius: 50%;_x000D_
  outline: none;_x000D_
}_x000D_
_x000D_
[slider] > input[type=range] {_x000D_
  position: absolute;_x000D_
  pointer-events: none;_x000D_
  -webkit-appearance: none;_x000D_
  z-index: 3;_x000D_
  height: 14px;_x000D_
  top: -2px;_x000D_
  width: 100%;_x000D_
  opacity: 0;_x000D_
}_x000D_
_x000D_
div[slider] > input[type=range]:focus::-webkit-slider-runnable-track {_x000D_
  background: transparent;_x000D_
  border: transparent;_x000D_
}_x000D_
_x000D_
div[slider] > input[type=range]:focus {_x000D_
  outline: none;_x000D_
}_x000D_
_x000D_
div[slider] > input[type=range]::-webkit-slider-thumb {_x000D_
  pointer-events: all;_x000D_
  width: 28px;_x000D_
  height: 28px;_x000D_
  border-radius: 0px;_x000D_
  border: 0 none;_x000D_
  background: red;_x000D_
  -webkit-appearance: none;_x000D_
}_x000D_
_x000D_
div[slider] > input[type=range]::-ms-fill-lower {_x000D_
  background: transparent;_x000D_
  border: 0 none;_x000D_
}_x000D_
_x000D_
div[slider] > input[type=range]::-ms-fill-upper {_x000D_
  background: transparent;_x000D_
  border: 0 none;_x000D_
}_x000D_
_x000D_
div[slider] > input[type=range]::-ms-tooltip {_x000D_
  display: none;_x000D_
}_x000D_
_x000D_
[slider] > div > [sign] {_x000D_
  opacity: 0;_x000D_
  position: absolute;_x000D_
  margin-left: -11px;_x000D_
  top: -39px;_x000D_
  z-index:3;_x000D_
  background-color: #d02128;_x000D_
  color: #fff;_x000D_
  width: 28px;_x000D_
  height: 28px;_x000D_
  border-radius: 28px;_x000D_
  -webkit-border-radius: 28px;_x000D_
  align-items: center;_x000D_
  -webkit-justify-content: center;_x000D_
  justify-content: center;_x000D_
  text-align: center;_x000D_
}_x000D_
_x000D_
[slider] > div > [sign]:after {_x000D_
  position: absolute;_x000D_
  content: '';_x000D_
  left: 0;_x000D_
  border-radius: 16px;_x000D_
  top: 19px;_x000D_
  border-left: 14px solid transparent;_x000D_
  border-right: 14px solid transparent;_x000D_
  border-top-width: 16px;_x000D_
  border-top-style: solid;_x000D_
  border-top-color: #d02128;_x000D_
}_x000D_
_x000D_
[slider] > div > [sign] > span {_x000D_
  font-size: 12px;_x000D_
  font-weight: 700;_x000D_
  line-height: 28px;_x000D_
}_x000D_
_x000D_
[slider]:hover > div > [sign] {_x000D_
  opacity: 1;_x000D_
}
_x000D_
<div slider id="slider-distance">_x000D_
  <div>_x000D_
    <div inverse-left style="width:70%;"></div>_x000D_
    <div inverse-right style="width:70%;"></div>_x000D_
    <div range style="left:0%;right:0%;"></div>_x000D_
    <span thumb style="left:0%;"></span>_x000D_
    <span thumb style="left:100%;"></span>_x000D_
    <div sign style="left:0%;">_x000D_
      <span id="value">0</span>_x000D_
    </div>_x000D_
    <div sign style="left:100%;">_x000D_
      <span id="value">100</span>_x000D_
    </div>_x000D_
  </div>_x000D_
  <input type="range" value="0" max="100" min="0" step="1" oninput="_x000D_
  this.value=Math.min(this.value,this.parentNode.childNodes[5].value-1);_x000D_
  let value = (this.value/parseInt(this.max))*100_x000D_
  var children = this.parentNode.childNodes[1].childNodes;_x000D_
  children[1].style.width=value+'%';_x000D_
  children[5].style.left=value+'%';_x000D_
  children[7].style.left=value+'%';children[11].style.left=value+'%';_x000D_
  children[11].childNodes[1].innerHTML=this.value;" />_x000D_
_x000D_
  <input type="range" value="100" max="100" min="0" step="1" oninput="_x000D_
  this.value=Math.max(this.value,this.parentNode.childNodes[3].value-(-1));_x000D_
  let value = (this.value/parseInt(this.max))*100_x000D_
  var children = this.parentNode.childNodes[1].childNodes;_x000D_
  children[3].style.width=(100-value)+'%';_x000D_
  children[5].style.right=(100-value)+'%';_x000D_
  children[9].style.left=value+'%';children[13].style.left=value+'%';_x000D_
  children[13].childNodes[1].innerHTML=this.value;" />_x000D_
</div>
_x000D_
_x000D_
_x000D_


The question was: "Is it possible to make a HTML5 slider with two input values, for example to select a price range? If so, how can it be done?"

Ten years ago the answer was probably 'No'. However, times have changed. In 2020 it is finally possible to create a fully accessible, native, non-jquery HTML5 slider with two thumbs for price ranges. If found this posted after I already created this solution and I thought that it would be nice to share my implementation here.

This implementation has been tested on mobile Chrome and Firefox (Android) and Chrome and Firefox (Linux). I am not sure about other platforms, but it should be quite good. I would love to get your feedback and improve this solution.

This solution allows multiple instances on one page and it consists of just two inputs (each) with descriptive labels for screen readers. You can set the thumb size in the amount of grid labels. Also, you can use touch, keyboard and mouse to interact with the slider. The value is updated during adjustment, due to the 'on input' event listener.

My first approach was to overlay the sliders and clip them. However, that resulted in complex code with a lot of browser dependencies. Then I recreated the solution with two sliders that were 'inline'. This is the solution you will find below.

_x000D_
_x000D_
var thumbsize = 14;

function draw(slider,splitvalue) {

    /* set function vars */
    var min = slider.querySelector('.min');
    var max = slider.querySelector('.max');
    var lower = slider.querySelector('.lower');
    var upper = slider.querySelector('.upper');
    var legend = slider.querySelector('.legend');
    var thumbsize = parseInt(slider.getAttribute('data-thumbsize'));
    var rangewidth = parseInt(slider.getAttribute('data-rangewidth'));
    var rangemin = parseInt(slider.getAttribute('data-rangemin'));
    var rangemax = parseInt(slider.getAttribute('data-rangemax'));

    /* set min and max attributes */
    min.setAttribute('max',splitvalue);
    max.setAttribute('min',splitvalue);

    /* set css */
    min.style.width = parseInt(thumbsize + ((splitvalue - rangemin)/(rangemax - rangemin))*(rangewidth - (2*thumbsize)))+'px';
    max.style.width = parseInt(thumbsize + ((rangemax - splitvalue)/(rangemax - rangemin))*(rangewidth - (2*thumbsize)))+'px';
    min.style.left = '0px';
    max.style.left = parseInt(min.style.width)+'px';
    min.style.top = lower.offsetHeight+'px';
    max.style.top = lower.offsetHeight+'px';
    legend.style.marginTop = min.offsetHeight+'px';
    slider.style.height = (lower.offsetHeight + min.offsetHeight + legend.offsetHeight)+'px';
    
    /* correct for 1 off at the end */
    if(max.value>(rangemax - 1)) max.setAttribute('data-value',rangemax);

    /* write value and labels */
    max.value = max.getAttribute('data-value'); 
    min.value = min.getAttribute('data-value');
    lower.innerHTML = min.getAttribute('data-value');
    upper.innerHTML = max.getAttribute('data-value');

}

function init(slider) {
    /* set function vars */
    var min = slider.querySelector('.min');
    var max = slider.querySelector('.max');
    var rangemin = parseInt(min.getAttribute('min'));
    var rangemax = parseInt(max.getAttribute('max'));
    var avgvalue = (rangemin + rangemax)/2;
    var legendnum = slider.getAttribute('data-legendnum');

    /* set data-values */
    min.setAttribute('data-value',rangemin);
    max.setAttribute('data-value',rangemax);
    
    /* set data vars */
    slider.setAttribute('data-rangemin',rangemin); 
    slider.setAttribute('data-rangemax',rangemax); 
    slider.setAttribute('data-thumbsize',thumbsize); 
    slider.setAttribute('data-rangewidth',slider.offsetWidth);

    /* write labels */
    var lower = document.createElement('span');
    var upper = document.createElement('span');
    lower.classList.add('lower','value');
    upper.classList.add('upper','value');
    lower.appendChild(document.createTextNode(rangemin));
    upper.appendChild(document.createTextNode(rangemax));
    slider.insertBefore(lower,min.previousElementSibling);
    slider.insertBefore(upper,min.previousElementSibling);
    
    /* write legend */
    var legend = document.createElement('div');
    legend.classList.add('legend');
    var legendvalues = [];
    for (var i = 0; i < legendnum; i++) {
        legendvalues[i] = document.createElement('div');
        var val = Math.round(rangemin+(i/(legendnum-1))*(rangemax - rangemin));
        legendvalues[i].appendChild(document.createTextNode(val));
        legend.appendChild(legendvalues[i]);

    } 
    slider.appendChild(legend);

    /* draw */
    draw(slider,avgvalue);

    /* events */
    min.addEventListener("input", function() {update(min);});
    max.addEventListener("input", function() {update(max);});
}

function update(el){
    /* set function vars */
    var slider = el.parentElement;
    var min = slider.querySelector('#min');
    var max = slider.querySelector('#max');
    var minvalue = Math.floor(min.value);
    var maxvalue = Math.floor(max.value);
    
    /* set inactive values before draw */
    min.setAttribute('data-value',minvalue);
    max.setAttribute('data-value',maxvalue);

    var avgvalue = (minvalue + maxvalue)/2;

    /* draw */
    draw(slider,avgvalue);
}

var sliders = document.querySelectorAll('.min-max-slider');
sliders.forEach( function(slider) {
    init(slider);
});
_x000D_
* {padding: 0; margin: 0;}
body {padding: 40px;}

.min-max-slider {position: relative; width: 200px; text-align: center; margin-bottom: 50px;}
.min-max-slider > label {display: none;}
span.value {height: 1.7em; font-weight: bold; display: inline-block;}
span.value.lower::before {content: "€"; display: inline-block;}
span.value.upper::before {content: "- €"; display: inline-block; margin-left: 0.4em;}
.min-max-slider > .legend {display: flex; justify-content: space-between;}
.min-max-slider > .legend > * {font-size: small; opacity: 0.25;}
.min-max-slider > input {cursor: pointer; position: absolute;}

/* webkit specific styling */
.min-max-slider > input {
  -webkit-appearance: none;
  outline: none!important;
  background: transparent;
  background-image: linear-gradient(to bottom, transparent 0%, transparent 30%, silver 30%, silver 60%, transparent 60%, transparent 100%);
}
.min-max-slider > input::-webkit-slider-thumb {
  -webkit-appearance: none; /* Override default look */
  appearance: none;
  width: 14px; /* Set a specific slider handle width */
  height: 14px; /* Slider handle height */
  background: #eee; /* Green background */
  cursor: pointer; /* Cursor on hover */
  border: 1px solid gray;
  border-radius: 100%;
}
.min-max-slider > input::-webkit-slider-runnable-track {cursor: pointer;}
_x000D_
<div class="min-max-slider" data-legendnum="2">
    <label for="min">Minimum price</label>
    <input id="min" class="min" name="min" type="range" step="1" min="0" max="3000" />
    <label for="max">Maximum price</label>
    <input id="max" class="max" name="max" type="range" step="1" min="0" max="3000" />
</div>
_x000D_
_x000D_
_x000D_

Note that you should keep the step size to 1 to prevent the values to change due to redraws/redraw bugs.

View online at: https://codepen.io/joosts/pen/rNLdxvK


I've been looking for a lightweight, dependency free dual slider for some time (it seemed crazy to import jQuery just for this) and there don't seem to be many out there. I ended up modifying @Wildhoney's code a bit and really like it.

_x000D_
_x000D_
function getVals(){_x000D_
  // Get slider values_x000D_
  var parent = this.parentNode;_x000D_
  var slides = parent.getElementsByTagName("input");_x000D_
    var slide1 = parseFloat( slides[0].value );_x000D_
    var slide2 = parseFloat( slides[1].value );_x000D_
  // Neither slider will clip the other, so make sure we determine which is larger_x000D_
  if( slide1 > slide2 ){ var tmp = slide2; slide2 = slide1; slide1 = tmp; }_x000D_
  _x000D_
  var displayElement = parent.getElementsByClassName("rangeValues")[0];_x000D_
      displayElement.innerHTML = slide1 + " - " + slide2;_x000D_
}_x000D_
_x000D_
window.onload = function(){_x000D_
  // Initialize Sliders_x000D_
  var sliderSections = document.getElementsByClassName("range-slider");_x000D_
      for( var x = 0; x < sliderSections.length; x++ ){_x000D_
        var sliders = sliderSections[x].getElementsByTagName("input");_x000D_
        for( var y = 0; y < sliders.length; y++ ){_x000D_
          if( sliders[y].type ==="range" ){_x000D_
            sliders[y].oninput = getVals;_x000D_
            // Manually trigger event first time to display values_x000D_
            sliders[y].oninput();_x000D_
          }_x000D_
        }_x000D_
      }_x000D_
}
_x000D_
  section.range-slider {_x000D_
    position: relative;_x000D_
    width: 200px;_x000D_
    height: 35px;_x000D_
    text-align: center;_x000D_
}_x000D_
_x000D_
section.range-slider input {_x000D_
    pointer-events: none;_x000D_
    position: absolute;_x000D_
    overflow: hidden;_x000D_
    left: 0;_x000D_
    top: 15px;_x000D_
    width: 200px;_x000D_
    outline: none;_x000D_
    height: 18px;_x000D_
    margin: 0;_x000D_
    padding: 0;_x000D_
}_x000D_
_x000D_
section.range-slider input::-webkit-slider-thumb {_x000D_
    pointer-events: all;_x000D_
    position: relative;_x000D_
    z-index: 1;_x000D_
    outline: 0;_x000D_
}_x000D_
_x000D_
section.range-slider input::-moz-range-thumb {_x000D_
    pointer-events: all;_x000D_
    position: relative;_x000D_
    z-index: 10;_x000D_
    -moz-appearance: none;_x000D_
    width: 9px;_x000D_
}_x000D_
_x000D_
section.range-slider input::-moz-range-track {_x000D_
    position: relative;_x000D_
    z-index: -1;_x000D_
    background-color: rgba(0, 0, 0, 1);_x000D_
    border: 0;_x000D_
}_x000D_
section.range-slider input:last-of-type::-moz-range-track {_x000D_
    -moz-appearance: none;_x000D_
    background: none transparent;_x000D_
    border: 0;_x000D_
}_x000D_
  section.range-slider input[type=range]::-moz-focus-outer {_x000D_
  border: 0;_x000D_
}
_x000D_
<!-- This block can be reused as many times as needed -->_x000D_
<section class="range-slider">_x000D_
  <span class="rangeValues"></span>_x000D_
  <input value="5" min="0" max="15" step="0.5" type="range">_x000D_
  <input value="10" min="0" max="15" step="0.5" type="range">_x000D_
</section>
_x000D_
_x000D_
_x000D_


Coming late, but noUiSlider avoids having a jQuery-ui dependency, which the accepted answer does not. Its only "caveat" is IE support is for IE9 and newer, if legacy IE is a deal breaker for you.

It's also free, open source and can be used in commercial projects without restrictions.

Installation: Download noUiSlider, extract the CSS and JS file somewhere in your site file system, and then link to the CSS from head and to JS from body:

<!-- In <head> -->
<link href="nouislider.min.css" rel="stylesheet">

<!-- In <body> -->
<script src="nouislider.min.js"></script>

Example usage: Creates a slider which goes from 0 to 100, and starts set to 20-80.

HTML:

<div id="slider">
</div>

JS:

var slider = document.getElementById('slider');

noUiSlider.create(slider, {
    start: [20, 80],
    connect: true,
    range: {
        'min': 0,
        'max': 100
    }
});

Examples related to forms

How do I hide the PHP explode delimiter from submitted form results? React - clearing an input value after form submit How to prevent page from reloading after form submit - JQuery Input type number "only numeric value" validation Redirecting to a page after submitting form in HTML Clearing input in vuejs form Cleanest way to reset forms Reactjs - Form input validation No value accessor for form control TypeScript-'s Angular Framework Error - "There is no directive with exportAs set to ngForm"

Examples related to html

Embed ruby within URL : Middleman Blog Please help me convert this script to a simple image slider Generating a list of pages (not posts) without the index file Why there is this "clear" class before footer? Is it possible to change the content HTML5 alert messages? Getting all files in directory with ajax DevTools failed to load SourceMap: Could not load content for chrome-extension How to set width of mat-table column in angular? How to open a link in new tab using angular? ERROR Error: Uncaught (in promise), Cannot match any routes. URL Segment

Examples related to slider

Change the Arrow buttons in Slick slider Touch move getting stuck Ignored attempt to cancel a touchmove Carousel with Thumbnails in Bootstrap 3.0 Very Simple Image Slider/Slideshow with left and right button. No autoplay Implementing a slider (SeekBar) in Android HTML5: Slider with two inputs possible? jQuery issue - #<an Object> has no method How to change Jquery UI Slider handle Is there a simple JavaScript slider?