<h3>Ticks & Labels</h3>
<div class="vr-2">
  <input 
    id="range-slider-input"
    type="range"
    role="slider"
    aria-valuemin="100"
    aria-valuemax="1000"
    aria-valuetext="$500"
    min="100"
    max="1000"
    step="100"
    value="400"
    values='{
      "100": "$100",
      "200": "$200",
      "300": "$300",
      "400": "$400",
      "500": "$500",
      "600": "$600",
      "700": "$700",
      "800": "$800",
      "900": "$900",
      "1000": "$1000"
    }'
   >
</div>

<h3>Ticks with First & Last Labels Only</h3>
<div class="vr-2">
  <input 
    id="range-slider-input-2"
    type="range"
    role="slider"
    aria-valuemin="100"
    aria-valuemax="1000"
    aria-valuetext="$500"
    min="100"
    max="1000"
    step="100"
    value="600"
    values='{
      "100": "$100",
      "200": "$200",
      "300": "$300",
      "400": "$400",
      "500": "$500",
      "600": "$600",
      "700": "$700",
      "800": "$800",
      "900": "$900",
      "1000": "$1000"
    }'
    first-last-labels-only
   >
</div>

<h3>Just Ticks</h3>
<div class="vr-2">
  <input 
    id="range-slider-input-3"
    type="range"
    role="slider"
    aria-valuemin="100"
    aria-valuemax="1000"
    aria-valuetext="$500"
    min="100"
    max="1000"
    step="100"
    value="300"
    values='{
      "100": "$100",
      "200": "$200",
      "300": "$300",
      "400": "$400",
      "500": "$500",
      "600": "$600",
      "700": "$700",
      "800": "$800",
      "900": "$900",
      "1000": "$1000"
    }'
    no-labels
   >
</div>

<h3>No Labels or Ticks</h3>
<div class="vr-2">
  <input 
    id="range-slider-input-4"
    type="range"
    role="slider"
    aria-valuemin="100"
    aria-valuemax="1000"
    aria-valuetext="$500"
    min="100"
    max="1000"
    step="100"
    value="700"
   >
</div>
@import url('https://fonts.googleapis.com/css?family=Raleway:400,600,800');

$blue: #2294d6;
$light-blue: #d8e0f3;
$light-gray: #eee;

@function remify($values) {
    $rems: ();

    @each $value in $values {
        @if $value == 0 or $value == auto {
            $rems: append($rems, $value);
        } @else {
            $rems: append(
                $rems,
                ($value / 17) + rem
            );
        }
    }

    @return $rems;
}

body {
  font-family: 'Raleway', Arial, Helvetica, sans-serif;
  font-weight: 300;
  padding: remify(32);
}

h3 {
  font-size: remify(18);
  font-weight: 800;
  margin: 0 0 remify(24);
}

.vr-2 {
  margin-bottom: remify(32);
}

.range-slider {
  display: flex;
  position: relative;
  height: remify(55);
  width: 100%;
  
  input[type='range'] {
    -webkit-appearance: none;
    -moz-appearance: none;
    background: $light-blue;
    width: calc(100% - 1px);
    height: remify(5);
    border-radius: remify(5);
    border: 0;
    margin: 0;
    padding: 0;
    position: relative;
    z-index: 3;

    &:focus {
      outline: 0;
      border-color: lighten($blue, 35%);
      
      &::-webkit-slider-thumb {
        background-color: darken($blue, 10%);
      }
      
      &::-moz-range-thumb {
        background-color: darken($blue, 10%);
      }
      
      &::-ms-thumb {
        background-color: darken($blue, 10%);
      }
    }
  }

  ::-webkit-slider-thumb {
    -webkit-appearance: none;
    border: remify(13) solid $blue;
    background-color: white;
    border-radius: 50%;
    width: remify(36);
    height: remify(36);
    box-shadow: remify(0 0 0 2) white;
    cursor: pointer;
  }

  ::-moz-range-thumb {
    appearance: none;
    -moz-appearance: none;
    border: remify(13) solid $blue;
    background-color: white;
    border-radius: 50%;
    width: remify(16);
    height: remify(16);
    box-shadow: remify(0 0 0 2) white;
    cursor: pointer;
  }

  ::-ms-thumb {
    border: remify(2) solid $blue;
    background-color: white;
    border-radius: 50%;
    width: remify(8);
    height: remify(8);
    cursor: pointer;
  }

  ::-webkit-runnable-track {
    -webkit-appearance: none;
    width: 0;
  }

  ::-moz-range-track {
    -moz-appearance: none;
    width: 0; // Firefox needs an explicit width for this to not show.
  }

  ::-ms-track {
    border-color: transparent;
    color: transparent;
    background-color: transparent;
  }

  ::-ms-fill-upper {
    background: transparent;
  }

  ::-ms-fill-lower {
    background: $blue;
    margin-left: remify(-2); // Fixes a gap in rendering in IE 11.
    height: remify(20);
    border-color: $blue;
    border-width: remify(20 0);
  }

  ::-ms-tooltip {
    display: none;
  }
}

.range-slider-ticks {
  position: absolute;
  top: remify(-5); 
  z-index: 2;
  left: 0;
  right: 0;
  display: flex;
  justify-content: space-between;
  margin: 0 9px;
  
  &__tick {
    background: $light-blue;
    display: inline-flex;
    justify-content: center;
    width: remify(15);
    height: remify(15);
    border-radius: 50%;
    position: relative;
    cursor: pointer;
  }
  
  &__label {
    font-size: remify(14);
    padding-top: remify(28);
    
    &.is-selected {
      font-weight: 800;
    }
  }
}
View Compiled
const createUniqueId = prefix => {
  return `${prefix}-${Math.round(Math.random() * 100000)}`;
}

class RangeSlider {
  
  constructor(el) {
    this.input = document.querySelector(el);

    this.el = this.input.getAttribute('id');
    this.minValue = this.input.getAttribute('min');
    this.maxValue = this.input.getAttribute('max');
    
    this.numTicks = this.maxValue / this.minValue;
    
    this.input.addEventListener('change', (e) => this.handleInputChange(e));
    
    this.wrapInput().then(wrapperId => {
      if (this.input.hasAttribute('values')) {
        this.values = JSON.parse(this.input.getAttribute('values'));
        this.createTicks(wrapperId);
      }
    });
  }
  
  handleInputChange(e) {
    const value = e.target.value;
    const ariaValueText = !this.values ? value : this.values[value];
    
    this.input.setAttribute('aria-valuetext', ariaValueText);
    
    if (this.input.hasAttribute('values')) {
      this.setSelectedLabel(this.values[value]); 
    }
  }
  
  handleLabelClick(label, value, e) {
    this.input.focus();
    this.input.value = value;
    this.input.setAttribute('value', value);
    this.input.setAttribute('aria-valuetext', label); 
    this.setSelectedLabel(label);
  }
  
  setSelectedLabel(label) {
    const selectedLabels = this.input.parentNode.querySelectorAll(`.range-slider-ticks__label`);
    
    [].forEach.call(selectedLabels, el => {
      el.classList[el.innerText === label ? 'add' : 'remove']('is-selected'); 
    });
  }
  
  wrapInput() {
    return new Promise(resolve => {
      const wrapper = document.createElement('div');
      const wrapperId = createUniqueId('range-slider');

      wrapper.id = wrapperId;
      wrapper.className = 'range-slider';
      
      this.input.parentNode.replaceChild(wrapper, this.input);

      document.querySelector(`#${wrapperId}`).appendChild(this.input);
      
      resolve(wrapperId);
    });
  }
  
  createTicks(wrapperId) {
    return new Promise(resolve => {
      let index = 0;
      let tickLabelText;
      
      const tickList = document.createElement('div');
      const tickListId = createUniqueId('tick-list');
      
      const noLabels = this.input.hasAttribute('no-labels');
      const firstAndLastLabelsOnly = this.input.hasAttribute('first-last-labels-only');

      tickList.id = tickListId;
      tickList.className = 'range-slider-ticks';

      document.querySelector(`#${wrapperId}`).appendChild(tickList);
      
      for (const prop in this.values) {
        if (this.values.hasOwnProperty(prop)) {
          
          const isFirstOrLastLabel = 
            !!firstAndLastLabelsOnly 
            && index > 0 
            && index < Object.values(this.values).length - 1;
          
          const tick = document.createElement('div'); 
          const tickLabel = document.createElement('span'); 

          tickLabel.className = `
            range-slider-ticks__label ${this.input.value === prop ? 'is-selected' : ''}
          `;
          
          if (!noLabels) {
            tickLabelText = document.createTextNode(
              isFirstOrLastLabel ? '' : this.values[prop]
            );
            
            tickLabel.appendChild(tickLabelText);
          }

          
          tick.className = 'range-slider-ticks__tick'; 
          tick.addEventListener('click', (e) => this.handleLabelClick(this.values[prop], prop, e));
          tick.appendChild(tickLabel);
          
          document.querySelector(`#${tickListId}`).appendChild(tick);
          
          index += 1;
        }
      }

      resolve();
    });
  }
}

[].forEach.call(document.querySelectorAll('input[type="range"]'), el => {
  const id = `#${el.getAttribute('id')}`;
  return new RangeSlider(id);
});

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.