Range sliders are a handy UI component for selecting a value within a specified range. While the default styling of range sliders provided by browsers might suffice for basic usage, customizing them can add a unique touch to your web application.
![](https://youssefbouhlal.com/wp-content/uploads/2024/03/custom-range-slider.gif)
1. HTML Structure
<div class="slider">
<input
type="range"
min="0"
max="100"
step="1"
name="custom_slider"
id="custom_slider"
value="50"
>
<span class="slider__value">
<span class="slider__value__num">50</span>
<span class="slider__value__unit">px</span>
</span>
<div class="slider__progress"></div>
</div>
2. CSS Styling
.slider {
--value: 50;
--min: 0;
--max: 100;
--primary-color: #2196f3;
--value-a: Clamp( var(--min), var(--value, 0), var(--max) );
--value-b: var(--value, 0);
--completed-a: calc( (var(--value-a) - var(--min)) / (var(--max) - var(--min)) * 100 );
--completed-b: calc( (var(--value-b) - var(--min)) / (var(--max) - var(--min)) * 100 );
--cb: Max(var(--completed-a), var(--completed-b));
--track-height: 4px;
--thumb-size: 18px;
--ticks-gap: 5px;
--value-offset-y: var(--ticks-gap);
display: inline-block;
height: 22px;
position: relative;
z-index: 1;
width: 300px;
}
.slider input[type=range] {
appearance: none;
width: 100%;
height: 22px;
margin: 0;
position: absolute;
left: 0;
top: 0;
cursor: grab;
outline: none;
background: none;
}
.slider input[type=range]::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: var(--thumb-size);
height: var(--thumb-size);
background: var(--primary-color);
border-radius: 50%;
border: 2px solid #FFF;
box-shadow: 0 0 10px 1px rgba(0,0,0,0.2);
cursor: pointer;
}
.slider input[type=range]::-moz-range-thumb {
width: var(--thumb-size);
height: var(--thumb-size);
background: var(--primary-color);
border-radius: 50%;
border: 2px solid #FFF;
box-shadow: 0 0 10px 1px rgba(0,0,0,0.2);
cursor: pointer;
}
.slider input[type=range] + .slider__value {
--value: var(--value-a);
--x-offset: calc(var(--completed-a) * -1%);
--pos: calc( ((var(--value) - var(--min)) / (var(--max) - var(--min))) * 100% );
--flip: -1;
position: absolute;
left: var(--pos);
z-index: 5;
display: flex;
font-size: 10px;
font-weight: bold;
transform: translate(var(--x-offset), calc( 150% * var(--flip) - (var(--y-offset, 0px) + var(--value-offset-y)) * var(--flip) ));
pointer-events: none;
}
.slider input[type=range] ~ .slider__progress {
--start-end: calc(var(--thumb-size) / 2);
--clip-end: calc(100% - (var(--cb)) * 1%);
--clip-start: 0;
--clip: inset(-20px var(--clip-end) -20px var(--clip-start));
position: absolute;
left: var(--start-end);
right: var(--start-end);
top: calc( var(--ticks-gap) * var(--flip-y, 0) + var(--thumb-size) / 2 - var(--track-height) / 2 );
height: calc(var(--track-height));
background: transparent;
pointer-events: none;
z-index: -1;
border-radius: 20px;
border: 1px solid var(--primary-color);
}
.slider input[type=range] ~ .slider__progress::before {
content: "";
position: absolute;
left: 0;
right: 0;
clip-path: var(--clip);
top: 0;
bottom: 0;
background: var(--primary-color);
z-index: 1;
border-radius: inherit;
}
.slider input[type=range] ~ .slider__progress::after {
content: "";
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
pointer-events: none;
border-radius: inherit;
}
3. JavaScript Functionality
document.addEventListener( 'DOMContentLoaded', () => {
const input = document.querySelector('.slider input[type=range]');
input.addEventListener( 'input', () => {
const value = input.value;
input.parentNode.style.setProperty( '--value', value );
const sliderValue = input.nextElementSibling;
const sliderValueNum = sliderValue.querySelector('.slider__value__num');
if ( sliderValueNum ) {
sliderValueNum.textContent = value;
}
});
});