Styling a native range input to be different heights before and after the thumb
Tags: css, webdev
Published: 2022-04-18 22:57:58

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

You can see my entire example at this CodePen [here](https://codepen.io/micahjones/pen/gOWXRjY).