DEV Community

Micah Jones
Micah Jones

Posted on

Styling a native range input to be different heights before and after the thumb

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);
}
Enter fullscreen mode Exit fullscreen mode

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: ???? */
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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: ???? */
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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 IE.

You can see my entire example at this CodePen here.

Top comments (0)