<div id="root"></div>
$thumb-size: 16px;

@mixin track-styles {
  appearance: none;
  background: transparent;
  border: transparent;
}

@mixin thumb-styles {
  appearance: none;
  pointer-events: all;
  width: $thumb-size;
  height: $thumb-size;
  border-radius: 0px;
  border: 0 none;
  cursor: grab;
  background-color: red;

  &:active {
    cursor: grabbing;
  }
}

#root {
  max-width: 500px;
  padding: 12px;
  margin: auto;
}

.wrapper {
  position: relative;
  display: flex;
  align-items: center;
  margin: 40px calc(#{$thumb-size} / 2);
  padding-top: 1.6rem;
  height: calc(#{$thumb-size} + 1.6rem);
}

.input-wrapper {
  width: calc(100% + #{$thumb-size});
  margin: 0 calc(#{$thumb-size} / -2);
  position: absolute;
  height: $thumb-size;
}

.control-wrapper {
  width: 100%;
  position: absolute;
  height: $thumb-size;
}

.input {
  position: absolute;
  width: 100%;
  pointer-events: none;
  appearance: none;
  height: 100%;
  opacity: 0;
  z-index: 3;
  padding: 0;

  &::-ms-track {
    @include track-styles;
  }

  &::-moz-range-track {
    @include track-styles;
  }

  &:focus::-webkit-slider-runnable-track {
    @include track-styles;
  }

  &::-ms-thumb {
    @include thumb-styles;
  }

  &::-moz-range-thumb {
    @include thumb-styles;
  }

  &::-webkit-slider-thumb {
    @include thumb-styles;
  }
};

.rail {
  position: absolute;
  width: 100%;
  top: 50%;
  transform: translateY(-50%);
  height: 6px;
  border-radius: 3px;
  background: lightgrey;
}

.inner-rail {
  position: absolute;
  height: 100%;
  background: hotpink;
  opacity: 0.5;
}

.control {
  width: $thumb-size;
  height: $thumb-size;
  border-radius: 50%;
  position: absolute;
  background: hotpink;
  top: 50%;
  margin-left: calc(#{$thumb-size} / -2);
  transform: translate3d(0, -50%, 0);
  z-index: 2;
}
View Compiled
import React from "https://cdn.skypack.dev/react@17.0.1";
import ReactDOM from "https://cdn.skypack.dev/react-dom@17.0.1";

const RangeSlider = ({ min, max, value, step, onChange }) => {
  const [minValue, setMinValue] = React.useState(value ? value.min : min);
  const [maxValue, setMaxValue] = React.useState(value ? value.max : max);

  React.useEffect(() => {
    if (value) {
      setMinValue(value.min);
      setMaxValue(value.max);
    }
  }, [value]);

  const handleMinChange = e => {
    e.preventDefault();
    const newMinVal = Math.min(+e.target.value, maxValue - step);
    if (!value) setMinValue(newMinVal);
    onChange({ min: newMinVal, max: maxValue });
  };

  const handleMaxChange = e => {
    e.preventDefault();
    const newMaxVal = Math.max(+e.target.value, minValue + step);
    if (!value) setMaxValue(newMaxVal);
    onChange({ min: minValue, max: newMaxVal });
  };

  const minPos = ((minValue - min) / (max - min)) * 100;
  const maxPos = ((maxValue - min) / (max - min)) * 100;

  return (
    <div class="wrapper">
      <div class="input-wrapper">
        <input
          class="input"
          type="range"
          value={minValue}
          min={min}
          max={max}
          step={step}
          onChange={handleMinChange}
        />
        <input
          class="input"
          type="range"
          value={maxValue}
          min={min}
          max={max}
          step={step}
          onChange={handleMaxChange}
        />
      </div>

      <div class="control-wrapper">
        <div class="control" style={{ left: `${minPos}%` }} />
        <div class="rail">
          <div
            class="inner-rail" 
            style={{ left: `${minPos}%`, right: `${100 - maxPos}%` }}
          />
        </div>
        <div class="control" style={{ left: `${maxPos}%` }} />
      </div>
    </div>
  );
};

const App = () => {
  const [value, setValue] = React.useState({ min: 0, max: 100 });

  return (
    <div>
      <RangeSlider min={0} max={100} step={5} value={value} onChange={setValue} />
      <p>The min value is: <span>{value.min}</span></p>
      <p>The max value is: <span>{value.max}</span></p>
    </div>
  );
}


ReactDOM.render(
  <App />,
  document.getElementById("root")
)
View Compiled

External CSS

  1. https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.4/css/bulma.min.css

External JavaScript

This Pen doesn't use any external JavaScript resources.