Did you ever faced with range inputs? They are really simple, right? You can pass min
and max
, perhaps step
as you can see below.
<input type="range" min="100" max="200" step="10">
But what if you need to create custom styled range input? Here comes the pain.
⬇️ tldr; if you just want the code, scroll down.
So, the range input have three parts. And if you want to implement it for yourself, probably you will use the same three parts as <div>
s with a plenty of JavaScript magic, mouse event handling and calculating the value out of relative sizes and positions.
Trackbar, Progressbar, Thumb.
There are a lot of articles in the wild about styling range inputs. Maybe the most comprehensive articles about this topic are from 2017.
- https://css-tricks.com/sliding-nightmare-understanding-range-input/
- https://css-tricks.com/styling-cross-browser-compatible-range-inputs-css/
So we have the three parts, let's start with Thumb. It's a simple button-like draggable thingie. Unfortunately we need to use different prefixes like in old times for browser compatibility.
input[type="range"]::-webkit-slider-thumb {
/* Styles for Chrome */
}
input[type="range"]::-moz-range-thumb {
/* Styles for Firefox */
}
input[type="range"]::-ms-thumb {
/* Styles for IE */
}
The following can be the Trackbar which is the range what Thumb can slide on. Here we have another three pseudo elements for browsers.
input[type="range"]::-webkit-slider-runnable-track {
/* Styles for Chrome */
}
input[type="range"]::-moz-range-track {
/* Styles for Firefox */
}
input[type="range"]::-ms-track {
/* Styles for IE */
}
Great, but can we create a Progressbar for it? Sure, let's see following:
input[type="range"]::-moz-range-progress {
/* Styles for Firefox */
}
input[type="range"]::-ms-fill-lower {
/* Styles for IE */
}
/* Styles for Chrome... ¯\_(ツ)_/¯ */
Yep, that's all folks, Chrome don't have styling for Progressbar. Although you can implement a moderately ugly workaround using CSS calc()
function, which is well supported in modern browsers. Besides CSS you will need some JS magic. The sad news are, pseudo elements can't be reached from JavaScript, but you can set CSS variables with it. Let's see the magic. You need to change only WebKit related styles of Trackbar.
input[type="range"] {
--webkitProgressPercent: 0%;
}
input[type="range"]::-webkit-slider-runnable-track {
background-image: linear-gradient(
90deg,
#f2f2f2 var(--webkitProgressPercent),
#262626 var(--webkitProgressPercent)
);
}
Now you only need to attach the --webkitProgressPercent
variable to Thumb's position. Here you will need to listen to some mouse events to achieve the native functionality. Rather I will attach here a working example which includes JS functionality as well.
PS, I didn't tested it in IE, only in Chrome and Firefox. I created a React component for this issue as well.
Now you are out of Range Input nightmare!😁
I hope this article was helpful for you, if you have a question or suggestion, let's discuss it in comments. And don't forget to like it. 🙏
Top comments (7)
Thanks for the post. When I recently had to style a range input, its default appearance on Chrome and Edge was similar to the design I was going for. I wanted to keep things simple, so I decided to not implement the whole JavaScript gradient thing and instead used some non-psuedo-element-based styles to style it on Chrome and Edge (such as accent-color). For Firefox, I did use the -moz-prefixed psuedo-elements. Unfortunately, Safari's default style was far from what I wanted, so I ended up targeting Safari specifically using a technique I found on a BrowserStack post and wrote a few styles using the -webkit-prefixed psuedo-elements to get it closer to what I wanted (but I decided to be OK with it not looking exactly like the desired design). I definitely found your article and CodePen helpful with deciding what to do.
@munkacsimark awesome post!
Is there a way to modify it to support multiple range inputs on the same webpage? It looks like adding additional range sliders does not pick up the special Chrome JS/styles
dev-to-uploads.s3.amazonaws.com/up...
Hi @jasontipton , thanks! 😁
The example was just for one input, there was only a
document.querySelector()
which returns one item. For handling multiple inputs you just need to select all of them withdocument.querySelectorAll()
and apply handler on each of them.I modified both examples to handle multiple items. 😊
@munkacsimark awesome solution! that makes sense, thank you!
@munkacsimark
found that the highlighted (--webkitProgressPercent) was not working in mobile (at least not in Chrome, I did not test the other mobile browsers). I was able to get it working though by adding to the JS.
Adding here in case anyone else needs it :)
I literally just created an account here to say thank you for this, I had to make a slight modification on RangeInput.jsx to get the different color on the left track to change but other than that, this worked like a charm
Thanks, I'm happy it helped you! 😁