a bug<\/a>. But it’s also immensely useful, so I’m not complaining.<\/p>\nThe structure inside<\/h3>\n Right from the start, we can see a source for potential problems: we have very different beasts inside for every browser.<\/p>\n
In Chrome, at the top of the shadow DOM, we have a div<\/code> we cannot access anymore. This used to be possible back when \/deep\/<\/code> was supported, but then the ability to pierce through the shadow barrier was deemed to be a bug, so what used to be a useful feature was dropped. Inside this div<\/code>, we have another one for the track and, within the track div<\/code>, we have a third div<\/code> for the thumb. These last two are both clearly labeled with an id<\/code> attribute, but another thing I find strange is that, while we can access the track with ::-webkit-slider-runnable-track<\/code> and the thumb with ::-webkit-slider-thumb<\/code>, only the track div<\/code> has a pseudo<\/code> attribute with this value.<\/p>\nInner structure in Chrome.<\/figcaption><\/figure>\nIn Firefox, we also see three div<\/code> elements inside, only this time they’re not nested – all three of them are siblings. Furthermore, they’re just plain div<\/code> elements, not labeled by any attribute, so we have no way of telling which is which component when looking at them for the first time. Fortunately, selecting them in the inspector highlights the corresponding component on the page and that’s how we can tell that the first is the track, the second is the progress and the third is the thumb.<\/p>\nInner structure in Firefox.<\/figcaption><\/figure>\nWe can access the track (first div<\/code>) with ::-moz-range-track<\/code>, the progress (second div<\/code>) with ::-moz-range-progress<\/code> and the thumb (last div<\/code>) with ::-moz-range-thumb<\/code>.<\/p>\nThe structure in Edge is much more complex, which, to a certain extent, allows for a greater degree of control over styling the slider. However, we can only access the elements with -ms-<\/code> prefixed IDs, which means there are also a lot of elements we cannot access, with baked in styles we’d often need to change, like the overflow: hidden<\/code> on the elements between the actual input<\/code> and its track or the transition<\/code> on the thumb’s parent.<\/p>\nInner structure in Edge.<\/figcaption><\/figure>\nHaving a different structure and being unable to access all the elements inside in order to style everything as we wish means that achieving the same result in all browsers can be very difficult, if not even impossible, even if having to use a different pseudo-element for every browser helps with setting individual styles.<\/p>\n
We should always aim to keep the individual styles to a minimum, but sometimes it’s just not possible, as setting the same style can produce very different results due to having different structures. For example, setting properties such as opacity<\/code> or filter<\/code> or even transform<\/code> on the track would also affect the thumb in Chrome and Edge (where it’s a child\/ descendant of the track), but not in Firefox (where it’s its sibling).<\/p>\nThe most efficient way I’ve found to set common styles is by using a Sass mixin because the following won’t work:<\/p>\n
input::-webkit-slider-runnable-track, \r\ninput::-moz-range-track, \r\ninput::-ms-track { \/* common styles *\/ }<\/code><\/pre>\nTo make it work, we’d need to write it like this:<\/p>\n
input::-webkit-slider-runnable-track { \/* common styles *\/ }\r\ninput::-moz-range-track { \/* common styles *\/ }\r\ninput::-ms-track { \/* common styles *\/ }<\/code><\/pre>\nBut that’s a lot of repetition and a maintainability nightmare. This is what makes the mixin solution the sanest option: we only have to write the common styles once so, if we decide to modify something in the common styles, then we only need to make that change in one place – in the mixin.<\/p>\n
@mixin track() { \/* common styles *\/ }\r\n\r\ninput {\r\n &::-webkit-slider-runnable-track { @include track }\r\n &::-moz-range-track { @include track }\r\n &::-ms-track { @include track }\r\n}<\/code><\/pre>\nNote that I’m using Sass here, but you may use any other preprocessor. Whatever you prefer is good as long as it avoids repetition and makes the code easier to maintain.<\/p>\n
Initial styles<\/h3>\n Next, we take a look at some of the default styles the slider and its components come with in order to better understand which properties need to be set explicitly to avoid visual inconsistencies between browsers.<\/p>\n
Just a warning in advance: things are messy and complicated. It’s not just that we have different defaults in different browsers, but also changing a property on one element may change another in an unexpected way (for example, when setting a background<\/code> also changes the color<\/code> and adds a border<\/code>).<\/p>\nWebKit browsers and Edge (because, yes, Edge also applies a lot of WebKit prefixed stuff) also have two levels of defaults for certain properties (for example those related to dimensions, borders, and backgrounds), if we may call them that – before setting -webkit-appearance: none<\/code> (without which the styles we set won’t work in these browsers) and after setting it. The focus is going to be however on the defaults after setting -webkit-appearance: none<\/code> because, in WebKit browsers, we cannot style the range input without setting this and the whole reason we’re going through all of this is to understand how we can make our lives easier when styling sliders.<\/p>\nNote that setting -webkit-appearance: none<\/code> on the range input<\/code> and on the thumb (the track already has it set by default for some reason) causes the slider to completely disappear in both Chrome and Edge. Why that happens is something we’ll discuss a bit later in this article.<\/p>\nThe actual range input element<\/h4>\n The first property I’ve thought about checking, box-sizing<\/code>, happens to have the same value in all browsers – content-box<\/code>. We can see this by looking up the box-sizing<\/code> property in the Computed<\/strong> tab in DevTools.<\/p>\nThe box-sizing<\/code> of the range input<\/code>, comparative look at all three browsers (from top to bottom: Chrome, Firefox, Edge).<\/figcaption><\/figure>\nSadly, that’s not an indication of what’s to come. This becomes obvious once we have a look at the properties that give us the element’s boxes – margin<\/code>, border<\/code>, padding<\/code>, width<\/code>, height<\/code>.<\/p>\nBy default, the margin<\/code> is 2px<\/code> in Chrome and Edge and 0 .7em<\/code> in Firefox.<\/p>\nBefore we move on, let’s see how we got the values above. The computed length values we get are always px<\/code> values.<\/p>\nHowever, Chrome shows us how browser styles were set (the user agent stylesheet rule sets on a grey background). Sometimes the computed values we get weren’t explicitly set, so that’s no use, but in this particular case, we can see that the margin<\/code> was indeed set as a px<\/code> value.<\/p>\nTracing browser styles in Chrome, the margin<\/code> case.<\/figcaption><\/figure>\nFirefox also lets us trace the source of the browser styles in some cases, as shown in the screenshot below:<\/p>\nTracing browser styles in Firefox and how this fails for the margin<\/code> of our range input<\/code>.<\/figcaption><\/figure>\nHowever, that doesn’t work in this particular case, so what we can do is look at the computed values in DevTools and then checking whether these computed values change in one of the following situations:<\/p>\n
\nWhen changing the font-size<\/code> on the input<\/code> or on the html<\/code>, which entails is was set as an em<\/code> or rem<\/code> value.<\/li>\nWhen changing the viewport, which indicates the value was set using %<\/code> values or viewport units. This can probably be safely skipped in a lot of cases though.<\/li>\n<\/ol>\nChanging the font-size<\/code> of the range input<\/code> in Firefox also changes its margin<\/code> value.<\/figcaption><\/figure>\nThe same goes for Edge, where we can trace where user styles come from, but not browser styles, so we need to check if the computed px<\/code> value depends on anything else.<\/p>\nChanging the font-size<\/code> of the range input<\/code> in Edge doesn’t change its margin<\/code> value.<\/figcaption><\/figure>\nIn any event, this all means margin<\/code> is a property we need to set explicitly in the input[type='range']<\/code> if we want to achieve a consistent look across browsers.<\/p>\nSince we’ve mentioned the font-size<\/code>, let’s check that as well. Sure enough, this is also inconsistent.<\/p>\nFirst off, we have 13.3333px<\/code> in Chrome and, in spite of the decimals that might suggest it’s the result of a computation where we divided a number by a multiple of 3<\/code>, it seems to have been set as such and doesn’t depend on the viewport dimensions or on the parent or root font-size<\/code>.<\/p>\nThe font-size<\/code> of the range input<\/code> in Chrome.<\/figcaption><\/figure>\nFirefox shows us the same computed value, except this seems to come from setting the font<\/code> shorthand to -moz-field<\/code>, which I was first very confused about, especially since background-color<\/code> is set to -moz-Field<\/code>, which ought to be the same since CSS keywords are case-insensitive. But if they’re the same, then how can it be a valid value for both properties? Apparently, this keyword is some sort of alias<\/a> for making the input look like what any input on the current OS looks like.<\/p>\nThe font-size<\/code> of the range input<\/code> in Firefox.<\/figcaption><\/figure>\nFinally, Edge gives us 16px<\/code> for its computed value and this seems to be either inherited from its parent or set as 1em<\/code>, as illustrated by the recording below:<\/p>\nThe font-size<\/code> of the range input<\/code> in Edge.<\/figcaption><\/figure>\nThis is important because we often want to set dimensions of sliders and controls (and their components) in general using em<\/code> units so that their size relative to that of the text on the page stays the same – they don’t look too small when we increase the size of the text or too big when we decrease the size of the text. And if we’re going to set dimensions in em<\/code> units, then having a noticeable font-size<\/code> difference between browsers here will result in our range input<\/code> being smaller in some browsers and bigger in others.<\/p>\nFor this reason, I always make sure to explicitly set a font-size<\/code> on the actual slider. Or I might set the font<\/code> shorthand, even though the other font-related properties don’t matter here at this point. Maybe they will in the future, but more on that later, when we discuss tick marks and tick mark labels.<\/p>\nBefore we move on to borders, let’s first see the color<\/code> property. In Chrome this is rgb(196,196,196)<\/code> (set as such), which makes it slightly lighter than silver<\/code> (rgb(192,192,192)<\/code>\/ #c0c0c0<\/code>), while in Edge and Firefox, the computed value is rgb(0,0,0)<\/code> (which is solid black<\/code>). We have no way of knowing how this value was set in Edge, but in Firefox, it was set via another similar keyword, -moz-fieldtext<\/code>.<\/p>\nThe color<\/code> of the range input<\/code>, comparative look at all three browsers (from top to bottom: Chrome, Firefox, Edge).<\/figcaption><\/figure>\nThe border<\/code> is set to initial<\/code> in Chrome, which is equivalent to none medium currentcolor<\/code> (values for border-style<\/code>, border-width<\/code> and border-color<\/code>). How thick a medium<\/code> border is exactly depends on the browser, though it’s at least as thick as a thin<\/code> one everywhere. In Chrome in particular, the computed value we get here is 0<\/code>.<\/p>\nThe border<\/code> of the range input<\/code> in Chrome.<\/figcaption><\/figure>\nIn Firefox, we also have a none medium currentcolor<\/code> value set for the border<\/code>, though here medium<\/code> seems to be equivalent to 0.566667px<\/code>, a value that doesn’t depend on the element or root font-size<\/code> or on the viewport dimensions.<\/p>\nThe border<\/code> of the range input<\/code> in Firefox.<\/figcaption><\/figure>\nWe can’t see how everything was set in Edge, but the computed values for border-style<\/code> and border-width<\/code> are none<\/code> and 0<\/code> respectively. The border-color<\/code> changes when we change the color<\/code> property, which means that, just like in the other browsers, it’s set to currentcolor<\/code>.<\/p>\nThe border<\/code> of the range input<\/code> in Edge.<\/figcaption><\/figure>\nThe padding<\/code> is 0<\/code> in both Chrome and Edge.<\/p>\nThe padding<\/code> of the range input<\/code>, comparative look at Chrome (top) and Edge (bottom).<\/figcaption><\/figure>\nHowever, if we want a pixel-perfect result, then we need to set it explicitly because it’s set to 1px<\/code> in Firefox.<\/p>\nThe padding<\/code> of the range input<\/code> in Firefox.<\/figcaption><\/figure>\nNow let’s take another detour and check the backgrounds before we try to make sense of the values for the dimensions. Here, we get that the computed value is transparent<\/code>\/ rgba(0, 0, 0, 0)<\/code> in Edge and Firefox, but rgb(255,255,255)<\/code> (solid white<\/code>) in Chrome.<\/p>\nThe background-color<\/code> of the range input<\/code>, comparative look at all three browsers (from top to bottom: Chrome, Firefox, Edge).<\/figcaption><\/figure>\nAnd… finally, let’s look at the dimensions. I’ve saved this for last because here is where things start to get really messy.<\/p>\n
Chrome and Edge both give us 129px<\/code> for the computed value of the width<\/code>. Unlike with previous properties, we can’t see this being set anywhere in Chrome, which would normally lead me to believe it’s something that depends either on the parent, stretching horizontally to fit as all block<\/code> elements do (which is definitely not the case here) or on the children. There’s also a -webkit-logical-width<\/code> property taking the same 129px<\/code> value in the Computed panel. I was a bit confused by this at first, but it turns out it’s the writing-mode relative equivalent<\/a> – in other words, it’s the width<\/code> for horizontal writing-mode and the height<\/code> for vertical writing-mode.<\/p>\nChanging the font-size<\/code> of the range input<\/code> in Chrome doesn’t change its width<\/code> value.<\/figcaption><\/figure>\nIn any event, it doesn’t depend on the font-size<\/code> of the input<\/code> itself or of that of the root element nor on the viewport dimensions in either browser.<\/p>\n