Styling an input range to have different track heights before and after the thumb

Micah Jones
AstroUXDS
Published in
4 min readApr 18, 2022
The cool kid on the slider block

Styling native HTML elements can be tricky, and the range input is certainly no different. I was tasked with updating our slider component, which uses the classic <input type=’range’>.

The Problem:

You probably know about styling the track and the thumb through pseudo classes such as ::-webkit-slider-runnable-track and ::-webkit-slider-thumb, or the Moz and MS versions of each. These are great, and allow you to style the track and thumb respectively, but what gets tricky is when you want the height of the track different on each side of the thumb, like my design called for.

My first thought was, “I’ll just ::before or ::after the thumb, and change the track height that way. Unfortunately, the ::before and ::after pseudo-elements don't work with the pseudo-classes for the track and thumb. Well, what now? I can change the height of the track, but that sets the height for the entire track, which we don’t want.

The Solution:

In order to get the track two different heights on either side of the thumb, you can take advantage of passing the background-image attribute two arguments. The caveat to this though, is that background-image doesn’t accept a straight color - so no hex or rgb values allowed. It does, however, accept gradients! So, you can do something like:

.slider::-webkit-runnable-track {
background-image:
linear-gradient(blue, blue), linear-gradient(blue, blue);
}

Adding two linear gradients of just blue won’t make it look any different right now from simply doing background-color: blue, but once we add the other background properties, some magic can happen.

The other background properties we’ll use are background-size, background-repeat , and background-position.

.slider::-webkit-runnable-track {
background-image:
linear-gradient(blue, blue), linear-gradient(blue, blue);
background-repeat: no-repeat no-repeat;
background-position: left;
/* background-size: ???? */
}

Setting the background repeat to no-repeat no-repeat makes the track color consistent, and setting the position to left left-centers the track.

The next property we’ll need is background-size and this is where we’ll need some sort of logic. In this example, I’ll be using vanilla JS and CSS custom properties.

We’ll need to set a CSS custom property, and then change that properties value based on events from the user. For reference, this is the HTML structure we’ll be using:

<div class="wrapper">
<div class='slider-wrapper'>
<input class='slider' type="range" max="100" min="0"></input>
</div>
</div>

First, we’ll need to create a CSS custom property to change inside of our .css file. For this, I’m using --valuePercent.

.wrapper{
--valuePercent = 50%;
}
.slider::-webkit-runnable-track {
background-image:
linear-gradient(blue, blue), linear-gradient(blue, blue);
background-repeat: no-repeat no-repeat;
background-position: left;
/* background-size: ???? */
}

Now that the CSS custom prop is created, we can modify it in our JS. Next, find the slider:

const slider = document.querySelector('.slider');

Then, we need to add an event listener to it and create a function that runs when that listener is triggered.

const slider = document.querySelector('.slider');
slider.addEventListener('input', updateValue);
function updateValue(){
const val = slider.value;
const valPercent = `${val}%`;
slider.style.setProperty('--valuePercent', valPercent);
}

Since this is an <input> element, we can listen to the input event. Our updateValue function then fires and in turn gets the current value of the slider, changes that to a percent string, and updates our CSS custom prop with that percent.

Now, we can use the background-size to determine when the track should be different heights!

.wrapper{
--valuePercent = 50%;
}
.slider::-webkit-runnable-track {
background-image:
linear-gradient(blue, blue), linear-gradient(blue, blue);
background-repeat: no-repeat no-repeat;
background-position: left;
background-size: var(--valuePercent) 5px, 100% 1px;
}

Because we have two background image’s, we can set each one of those to a different width and height using background-size. Now, the width of the first background image is equal to --valuePercent, and will be 5px tall. The second background size is set to 100% width, which means that the entire track will be effected by the 1px height. But since the left side of the thumb is 5px, that’s what we’ll see.

Now our track is two different heights on either side of the thumb! You can now use the ::-webkit-slider-thumb and the other div classes to style this however you’d like. Keep in mind, this implementation is only using webkit, and other browsers use different prefixes: -mz for Firefox and -ms for Internet explorer.

You can see my entire example at this CodePen here.

--

--