Styling input range sliders

Styling input range sliders

Input range slider meme

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
  1. Open the DevTools (F12)

  2. Click the gear icon at the top right corner

  3. In the Preferences tab, under the Elements section, enable the Show user agent shadow DOM option

  • Firefox
  1. In the address bar type about:config, hit Enter, and click Accept the Risk and Continue

  2. Search for devtools.inspector.showAllAnonymousContent and set it to true

After doing the steps above you can check your <input type="range" /> element in the DevTools and see what is actually rendered:

  • Chrome

Input range slider shadow DOM in Chrome

  • Firefox

Input range slider shadow DOM in Firefox

3. Pseudo-Elements

Each browser allows us to target these shadow elements using different pseudo-elements/selectors:

  • Chrome

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:

  1. CSS

    We can cast a shadow from the thumb to the start of the track and clip it to give the impression of it being the same height as the track. (example)

  2. JavaScript

    We can get the position of the thumb and set the background of the track to be a linear-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 the rotate property seems to glitch the slider movement somehow. if we use transform: rotate(-90deg); instead, everything works fine and it won't get stuck. so just do that and ignore the next two sections ^^;

Vertical slider meme

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.