How to Create a Custom Range Slider Using CSS

Temani Afif
Temani Afif
Share

In this article, I’ll show how to use modern CSS techniques to create an eye-catching, custom range slider with nothing but the native HTML <input> element.

Table of Contents

Range sliders (<input type="range">) let users choose a value within a given range, providing an alternative input types such as <input type="number">.

The default range slider styles don’t look great. The image below gives an idea of how range the sliders we’ll be styling are displayed by default in Chrome, Firefox and Safari.

Default range sliders in Chrome, Firefox and Safari

But <input> elements are hard to style. Most of the online solutions for styling them rely on JavaScript and some cluttered code. Worse still, some of these techniques also break the accessibility of the element.

So let’s look at how to do things better, using just CSS, and without compromising accessibility. The CodePen demo below shows what we’ll be building.

See the Pen CSS only custom range sliders by Temani Afif (@t_afif) on CodePen.

Cool right? You’ll also find variations on these styles at the end of the article!

The Structure of the Range Input Element

Let’s start by dissecting the structure of the range input element. It’s a native element, and each browser has its own implementation of such elements. We mainly have two different implementations.

There’s one for Webkit and Blink browsers such as Chrome, Edge, Safari, and Opera:

<input type="range" min="0" max="100" step="1" value="20">
  <div>
    <div pseudo="-webkit-slider-runnable-track" id="track">
      <div id="thumb">
    </div>
   </div>
  </div>
</input>

Overview of the Chrome input

And this is the one for Firefox:

<input type="range" min="0" max="100" step="1" value="20">
  <div></div>
  <div></div>
  <div></div>
</input>

Overview of the Firefox input

There’s a third implementation for IE, but thankfully that browser is all but dead and gone now!

Such inconsistency between browsers is what makes the task difficult, as we need to provide different styling for each implementation. I won’t dig more into this, as the article would never end, but I highly recommend reading this article by Ana Tudor for more in-depth exploration.

The only thing you need to keep in mind is that, whatever the implementation, we always have the “thumb” as a common component.

Showing the thumb element

I’m only going to style this element, which will make my custom range slider easy to customize. Let’s jump straight into the code to see the magic in play.

Customizing the Input

The first step is to reset and disable all the browser default styles by using appearance: none and some other common properties:

input {
  appearance :none;
  background: none;
  cursor: pointer;
}

In a more complicated scenario, we may need to add more code in case other default styles are applied to our element. Simply just need to make sure we have a “naked” element without any visual styling.

Let’s also define a few CSS variables so we can easily create different variations for the range slider:

input {
  --c: orange; /* active color */
  --g: 8px; /* the gap */
  --l: 5px; /* line thickness*/
  --s: 30px; /* thumb size*/

  width: 400px; /* input width */
  height: var(--s); 
  appearance :none;
  background: none;
  cursor: pointer;
}

At this step, only the thumb is visible with its default styles, as the CodePen demo below shows.

Styling the Thumb Element

Let’s style the thumb element. We’ll start with the basic setup:

<thumb selector> {
  height: var(--s);
  aspect-ratio: 1;
  border-radius: 50%;
  box-shadow: 0 0 0 var(--l) inset var(--c);
  appearance: none;
}

The code should be self-explanatory. Nothing fancy so far, and we’ll get the result shown below.

Note the use of two different selectors, as we explained in the first section:

/* Chrome, Edge, Safari, Opera */
input[type="range" i]::-webkit-slider-thumb { }
/* Firefox */
input[type="range"]::-moz-range-thumb { }

But how do you know the selector to use?

I’ve simply inspected the code of the input using the browser’s developer tools to see the selector each browser is using to style the thumb. The article I shared previously shows you how to manipulate the developer tools to get such information.

Adding Some Magic with border-image

Now we’re going to use a magic CSS trick to complete our slider. It involves the use of border-image:

border-image: linear-gradient(90deg,var(--c) 50%,#ababab 0) 1/0 100vw/0 calc(100vw + var(--g));

I know it looks scary, but let’s dissect that line and you will see it’s not that difficult. The above is the shorthand for the following:

border-image-source: linear-gradient(90deg,var(--c) 50%,#ababab 0); 
border-image-slice: 1;
border-image-width: 0 100vw;
border-image-outset: 0 calc(100vw + var(--g));

From the MDN page, we read:

The border-image CSS property draws an image around a given element. It replaces the element’s regular border.

Our image will be a gradient having two colors — the main one (defined by --c), and a gray color. We need the border image to cover the whole space of the input horizontally, so we use a big value for the left and right width (100vw) while we keep the top and bottom at (0).

But the border-image-width is limited to the element size. To overcome this, we need to also use a big value for the border-image-outset to increase the space available for the border image. From MDN again:

The border-image-outset CSS property sets the distance by which an element’s border image is set out from its border box.

The parts of the border image that are rendered outside the element’s border box with border-image-outset do not trigger overflow scrollbars and don’t capture mouse events.

When you first see the slider, it looks like we’re increasing the main color on the left, but in reality we’re sliding a fixed gradient that’s overflowing our element.

The following demo provides an overview of what’s happening under the hood.

Drag the thumb and slide it to see how things are moving. I’m using a small value for the width and outset so we can easily understand the trick.

By adding overflow: hidden to the input element and using a big value, the illusion is perfect, as shown below.

See the Pen Adding overflow by SitePoint (@SitePoint) on CodePen.

If you want to dive deeper into the border-image and learn more about “width” and “outset” check out my article where I dissect the border-image property if you want to learn all its secrets and understand the values I’m using.

That’s it! We have a custom range slider with a few lines of code that you can easily control by adjusting a few variables.

See the Pen Final range slider by SitePoint (@SitePoint) on CodePen.

Adding Some Animation

What about some subtle animation when we interact with the slider? It doesn’t need a lot of code, and it will enhance the UX of the slider.

First, we’re going to transform the thumb from a border-only circle into a full circle when we click on it. For this, we increase the spread value of the box-shadow. Remember that we’ve used box-shadow to define the border of the thumb:

box-shadow: 0 0 0 var(--l) inset var(--c);

We update the var(--l) to var(--s) using the :active selector and the :focus-visible. The latter is related to keyboard navigation and allows us to have the same effect whether we use the mouse or the keyboard.

The code is as follows:

input[type="range" i]::-webkit-slider-thumb {
  box-shadow: 0 0 0 var(--l) inset var(--c);
  transition: .3s;
}

input[type="range" i]::-moz-range-thumb {
  box-shadow: 0 0 0 var(--l) inset var(--c);
  transition: .3s;
}

input:active::-webkit-slider-thumb,
input:focus-visible::-webkit-slider-thumb {
  box-shadow: 0 0 0 var(--s) inset var(--c);
}

input:active::-moz-range-thumb,
input:focus-visible::-moz-range-thumb {
  box-shadow: 0 0 0 var(--s) inset var(--c);
}

It’s a bit lengthy for a box-shadow transition, right? We can optimize it using a CSS variable:

input[type="range" i]::-webkit-slider-thumb {
  box-shadow: 0 0 0 var(--_b,var(--l)) inset var(--c);
  transition: .3s;
}

input[type="range" i]::-moz-range-thumb {
  box-shadow: 0 0 0 var(--_b,var(--l)) inset var(--c);
  transition: .3s;
}

input:active,
input:focus-visible {
  --_b:  var(--s);
}

I’m expressing the spread value using a variable, and I’m simply updating that variable on :active and :focus-visible.

I’m also going to add a little animation to the color. I’ll make it a bit darker on :hover. For this, I won’t update the color, but rather mix it with black using the new color-mix() function. This trick allows us to use the main color defined by --c rather than define a new dark color for each slider manually:

--_c: color-mix(in srgb, var(--c), #000 var(--p,0%));

I’m defining a new variable that will replace the --c in the code. Then by adjusting the percentage of the black color (#000) using the variable --p, I’m controlling the “darkness” of the color:

input:focus-visible,
input:hover{
  --p: 25%;
}

This feature isn’t yet supported in every browser, so the use of a fallback is highly recommended:

@supports not (color: color-mix(in srgb,red,red)) {
  input {
    --_c: var(--c); /* if no support we keep the color as defined */
  }
}

Our range slider is now perfect!

See the Pen CSS only custom range sliders by Temani Afif (@t_afif) on CodePen.

Conclusion

We’ve reached the end and haven’t had to deal with any complex browser-related implementation! We identified the selector of the thumb element and, using a few CSS tricks, we styled the whole range slider with it. Let’s not forget that we did this using only the <input> element, so we don’t have to worry about any accessibility issues, as we’ve kept the native functionality. The slider supports keyboard navigation without a problem.

Here are more examples of sliders using the same technique. I’ll let you dissect their code as an exercise.

See the Pen Custom range sliders by Temani Afif (@t_afif) on CodePen.

See the Pen Custom range sliders by Temani Afif (@t_afif) on CodePen.

Frequently Asked Questions (FAQs) about CSS Custom Range Slider

How can I style the thumb of a CSS custom range slider?

Styling the thumb of a CSS custom range slider involves using the ::-webkit-slider-thumb pseudo-element. This pseudo-element targets the thumb of the slider. You can then apply various CSS properties to style it. For instance, you can change the background color, border, height, width, and border-radius to achieve your desired look. Remember, this pseudo-element is webkit-specific, meaning it works in browsers like Chrome and Safari. For Firefox, use ::-moz-range-thumb.

How can I make a vertical CSS custom range slider?

To create a vertical CSS custom range slider, you need to rotate the slider using the transform property. Set the transform property to rotate(270deg) to rotate the slider 270 degrees. This will make the slider vertical. However, keep in mind that this rotation will also reverse the direction of the slider. To fix this, you can reverse the values of the slider.

How can I add labels to a CSS custom range slider?

Adding labels to a CSS custom range slider can be achieved by using HTML and CSS. You can create a div element for each label and position them using CSS. The labels can be positioned based on the percentage value of the slider’s value. This way, each label will correspond to a specific value on the slider.

How can I create a multi-thumb CSS custom range slider?

Creating a multi-thumb CSS custom range slider is a bit more complex as it involves using multiple range inputs and positioning them on top of each other. Each range input represents a thumb on the slider. You can then use JavaScript to synchronize the values of the thumbs and prevent them from crossing each other.

How can I make a CSS custom range slider responsive?

Making a CSS custom range slider responsive involves using relative units like percentages instead of absolute units like pixels. This way, the slider will adjust its size based on the size of its parent element. You can also use media queries to adjust the size and layout of the slider on different screen sizes.

How can I add ticks or steps to a CSS custom range slider?

Adding ticks or steps to a CSS custom range slider can be achieved by using the step attribute in the range input. The step attribute specifies the interval between each value on the slider. You can then use CSS to create visual ticks that correspond to these steps.

How can I change the color of a CSS custom range slider?

Changing the color of a CSS custom range slider involves using the ::-webkit-slider-runnable-track and ::-webkit-slider-thumb pseudo-elements. You can change the background property of these pseudo-elements to change the color of the track and the thumb respectively. For Firefox, use ::-moz-range-track and ::-moz-range-thumb.

How can I disable a CSS custom range slider?

Disabling a CSS custom range slider can be done by using the disabled attribute in the range input. When the disabled attribute is present, the slider will be grayed out and the user won’t be able to interact with it.

How can I create a CSS custom range slider without JavaScript?

Creating a CSS custom range slider without JavaScript is possible but it limits the functionality of the slider. You can use the range input and style it using CSS. However, without JavaScript, you won’t be able to create features like multi-thumb sliders or synchronize the slider value with other elements.

How can I create a CSS custom range slider with a tooltip?

Creating a CSS custom range slider with a tooltip involves using a combination of HTML, CSS, and JavaScript. The tooltip can be an HTML element that is positioned over the thumb of the slider. You can then use JavaScript to update the content of the tooltip based on the value of the slider.