Styling input range sliders might seem easy at first but there are a lot of caveats and it's more complicated than it seems. in this article, I will go through what is needed to have working, styled horizontal and vertical input range sliders.
1. The Shadow DOM
When you add the <input type="range" />
line to your HTML, under the hood, browsers add multiple additional DOM elements to actually render it on the page. this implementation differs for each browser and is something you need to keep in mind when styling different parts of the slider. These DOM elements are hidden by default and are called shadow DOM elements for that reason. the first step is to actually see what the structure of these elements looks like.
2. Revealing the Shadow DOM
To see the shadow DOM elements, you need to change some settings in your browser's DevTools.
- Chrome
Open the DevTools (F12)
Click the
gear
icon at the top right cornerIn the
Preferences
tab, under theElements
section, enable theShow user agent shadow DOM
option
- Firefox
In the address bar type
about:config
, hit Enter, and clickAccept the Risk and Continue
Search for
devtools.inspector.showAllAnonymousContent
and set it totrue
After doing the steps above you can check your <input type="range" />
element in the DevTools and see what is actually rendered:
- Chrome
- Firefox
3. Pseudo-Elements
Each browser allows us to target these shadow elements using different pseudo-elements/selectors:
- Chrome
Thumb:
::-webkit-slider-thumb
Firefox
- Track:
::-moz-range-track
Thumb:
::-moz-range-thumb
Progress:
::-moz-range-progress
4. Styling
Now that we know the structure of the shadow elements and how to target them using pseudo-elements/selectors, we can start styling them. but before doing that, we need to remove the default style (appearance) applied by the browser. we can do it using the appearance
CSS property:
appearance: none;
We can also set the background to transparent to get rid of the default background color:
background-color: transparent;
Now we can apply our custom styles using the pseudo-elements/selectors. A simple style (using flashy colors for demonstration purposes) would look like this:
As you can see both from the shadow DOM elements and the pseudo-elements/selectors, Chrome does not provide a progress
element for us to style. but we can achieve a similar result by doing one of the following:
CSS
We can cast a shadow from the
thumb
to the start of thetrack
and clip it to give the impression of it being the same height as the track. (example)JavaScript
We can get the position of the
thumb
and set the background of thetrack
to be alinear-gradient
that's one color up to that position and another to the end. (example)
ℹ️ We can also use
:hover
,:active
, and:disabled
selectors to further customize the appearance in different states.
5. Vertical Slider
Edit: After playing around with it a bit more, I've figured out the problem. I was using
rotate : -90deg;
to rotate the horizontal slider and make a vertical one. using therotate
property seems to glitch the slider movement somehow. if we usetransform: rotate(-90deg);
instead, everything works fine and it won't get stuck. so just do that and ignore the next two sections ^^;
If we rotate our horizontal slider to make a vertical one, it will look and work fine on desktop but on mobile/touch it won't work properly and will be stuck. to address this issue, each browser provides an extra property we can use to tell it to render a vertical slider instead of a horizontal one:
- Chrome
-webkit-appearance: slider-vertical;
- Firefox
<input type="range" orient="vertical" />
But there's a problem. before, we set the appearance
to none
to get rid of the default styles but setting these properties will override our styles and revert the slider to how it looked like before.
To work around this, what I came up with is rotating our styled slider to make a vertical but non-functional slider. then we put a working vertical slider made using the properties above, on top of it, hide it, and when its value changes, we update our original slider. so what users interact with will be the default vertical slider but what they see is our styled slider. (we only need to do this for mobile/touch)
⚠️ Still on Firefox sometimes the vertical slider gets stuck on mobile/touch.
ℹ️ We also need to add
writing-mode: bt-lr;
for older versions of Edge.
6. orient="vertical" in React/TypeScript
After adding orient="vertical"
to the input
tag in React/TypeScript, we will get this error:
Property 'orient' does not exist on type 'DetailedHTMLProps<InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>'
According to React, the orient
property does not exist on the input
tag, so we have to add the property ourselves. we can use module declare
to do this. we can either add the following lines to the top of the current file or place it in a index.d.ts
file:
declare module 'react' {
export interface InputHTMLAttributes<T> {
orient?: string;
}
}
ℹ️ You can use this method to add any custom property to any HTML tag you want.
🎉 Congratulations! Now, you have probably given up on customizing the input range slider and have decided to either use the default browser style or a component library!
Whether you're brave enough to do it or decided against it, hopefully, you've learned something from reading this article that could help you in the future. :)
P.S.
Styling for Safari, Edge, Opera, Brave, etc. would be similar to Chrome since they either use Webkit or are compatible with it.
I've ignored Internet Explorer throughout this article but it has its own set of pseudo-elements you need to style separately.