Safari Ripper ☠️

Security researcher Sabri posted a bit of code that will “force restart any iOS device.” It’s interesting to see HTML & CSS have this kind of dangerous power. It’s essentially a ton of <div>s scaled to be pretty huge and then set over a repeating JPG image with each <div> blurring the background via backdrop-filter. It must cause such extreme and unhandled memory usage that it wreaks havoc on the browser as well as the entire operating system.

I was trying to test it out myself and be really careful not to execute it… but of course I did, and it crashed my Chrome 68 on a MacBook Pro. Not the whole operating system, but I had to force quit the browser. Then again, I suppose even while(true) {} can do that!

The comment thread on the gist hast more interesting details, like how it crashes iOS Safari 9+ (including the new version 12!) and weird behavior on the PlayStation 3 native browser.

Direct Link to Article — Permalink

The post Safari Ripper ☠️ appeared first on CSS-Tricks.

Browser painting and considerations for web performance

The process of a web browser turning HTML, CSS, and JavaScript into a finished visual representation is quite complex and involves a good bit of magic. Here’s a simplified set of steps the browser goes through:

  1. Browser creates the DOM and CSSOM.
  2. Browser creates the render tree, where the DOM and styles from the CSSOM are taken into account (display: none elements are avoided).
  3. Browser computes the geometry of the layout and its elements based on the render tree.
  4. Browser paints pixel by pixel to create the visual representation we see on the screen.

In this article, I’d like to focus on the last part: painting.

All of those steps combined is a lot of work for a browser to do on load… and actually, not just on load, but any time the DOM (or CSSOM) is changed. That’s why many web developers tend to partially solve this by using some sort of frontend framework, such as React which, apart from many other advantages, can help to highly optimize changes in the DOM to avoid unnecessary recalculating or rendering.

You may have heard terms such as state, component rendering, or immutability. All of those have something to do with optimization of DOM changes, or in other words, to only make changes to the DOM when it’s necessary.

To give an example, the state of a web application may change, and that would lead to a change in UI. However, certain (or many) components are not affected by this change. What React helps to do is limit the writing to the DOM for elements that are actually affected by a change in state and ultimately limit the rendering to the smallest part of the web application possible:

DOM/CSSOM → render tree → layout → painting

However, browser painting is special in its own way, as it can happen even without any changes to the DOM and/or CSSOM.

Example of page performance summary

The diagram above was generated using Chrome’s performance panel in DevTools (more on that later) and it shows how much time was taken by each task in the browser in the recorded time (0-7.12s) after reloading of a page. As you can see, painting takes a significant part, and that’s not automatically a bad thing. In this particular example, the increased painting is caused by a combination of animated GIFs on the page and canvas drawing (at 60fps), where both don’t cause any changes to the DOM or its styles, while still triggering painting.

Another good example of a feature that may cause painting without any outside intervention is the CSS animation property, and compared to animated GIF or canvas, it is probably more common on the web. An animation is usually triggered by user input, like hover, but thanks to animation and @keyframes rules, we can even create quite complex animations running constantly on the page without much of an effort, which is pretty amazing.

What some might not realize, is that those animations can easily get out of hand and constantly trigger painting, and that can cost us a lot of processing power. Of course, there are some rules that can be used to avoid painting. Most obvious is limiting manipulation of elements to CSS transform and opacity properties, which by default don’t trigger paint, unless some special circumstances are in place, such as animating an SVG path.

Paint flashing

You likely know that Chrome has DevTools. What you might not know about is a little shortcut (Shift+Cmd+P on Mac or Control+Shift+P on PC) which can be used inside DevTools to bring up a little search bar and command menu.

Command Menu

I’ve started digging around it, and apart from many other useful and incredibly interesting options, a render panel caught my attention.

Render Panel

At the first sight, you can see some interesting options that can be very helpful when it comes to debugging animation on the web, like an transform property, which as we covered, doesn’t cause painting. The painting was present where one would expect it to be, like changes in text color on hover, but that’s not something that should be much of a concern due to its area and presence only on hover of the element. To sum it up, you can always find something to improve, even if you wrote the code yesterday…

But one thing was a slap in the face.

It doesn’t matter how experienced or careful you are, you can — and most likely will — make a mistake. We’re just people and some would argue that fixing your own bugs is most of the job when it comes to development. However, for a bug to be fixable, we need to be aware of it… and that’s exactly where the render panel helps.

Case study

Let’s take a closer look at the actual issue. The design came in with the request for a noisy background. That kind of effect that old TVs had when there was no signal.

It is known that GIFs have many issues, where performance is certainly one of them, so I definitely couldn’t use that for a whole page background. If you’d like to read some more on why to avoid GIFs, here is a good resource with a bunch of reasons.

Using JavaScript is definitely an option in this case. Displaying or hiding elements with a slightly moved background was the first thing that came to my mind, and using canvas could help too. However, all of this seemed a little overkill for simply having a background. I decided to go for a CSS-only approach.

My solution was to take a small “noisy” PNG image as a background-image, enable background-repeat and throw it over a one-color background. How did I achieve the noise effect? With infinite CSS animation! By setting the background-position to different value over the period of 200 milliseconds. Here’s how that turned out:

See the Pen MXoddr by Georgy Marchuk (@gmrchk) on CodePen.

Can you guess the problem? It seemed like a quite an elegant solution to me, and I was excited about my achievement of making it through without a crappy GIF and even not a single line of JavaScript. Just simple CSS that is optimized in browsers these days.

Well, the paint flashing showed something completely different. The layer of the size of the window was constantly repainting, without the user even doing anything. You can see the paint flashing in the demo above if you enable it in the render panel (note that paint flashing doesn’t show up in embedded pen).

Without paint flashing (left) vs. with paint flashing (right)

That certainly doesn’t play well with the performance of the website and drains laptop batteries like there’s no tomorrow.

CPU usage for the animation done with background-position (top) and transform (bottom)

All of this CPU usage could have been avoided by replacing the changes to background-position using transform or opacity.

See the Pen XYOYGm by Georgy Marchuk (@gmrchk) on CodePen.

The problem

I’ve been doing web development for a while and I knew very well that animating a background is never a good idea. This felt like a rookie mistake. People make mistakes… but that’s not the whole story. The website was all laggy and uncomfortable to navigate. How did I miss it?

Something that certainly plays a big role is the fact that I am (and you may be as well) a little spoiled when it comes to development equipment. I have a nice, powerful computer for work and access to speedy internet. Unless we write some really crappy code, anything we write runs quite smoothly in our eyes. But that’s not always the case for our users.

A similar problem applies to many other things — like display size. Using a little exaggeration, while we are developing on 27” display with 4K resolution and getting the designs primarily for 1920×1080, our visitors come in mainly from 1366×768 laptops and have a completely different workflow when it comes to using a computer.

Conclusion

While this article started off as a piece about painting, its main topic is really much more about being mindful of the impact our code has on the painting process or performance in general. While painting serves as a good example of something that can be problematic and easily missed, it’s more of a disconnect between developer and user that is the issue.

The web is a place of many environments, where the developer’s environment is often far different than the user’s. While there is no need to change our ways or switch to lazy computers, it definitely helps to see our work the way it is seen by others from time to time. My suggestion is: when you come home from work and have a little free time, try to pick up your old computer and check your work there, to get a little closer to what your users experience.

If you don’t have this kind of computer around, tools like render panel can turn out to be awfully handy.

The post Browser painting and considerations for web performance appeared first on CSS-Tricks.

A Short History of WaSP and Why Web Standards Matter

In August of 2013, Aaron Gustafson posted to the WaSP blog. He had a bittersweet message for a community that he had helped lead:

Thanks to the hard work of countless WaSP members and supporters (like you), Tim Berners-Lee’s vision of the web as an open, accessible, and universal community is largely the reality. While there is still work to be done, the sting of the WaSP is no longer necessary. And so it is time for us to close down The Web Standards Project.

If there’s just the slightest hint of wistful regret in Gustafson’s message, it’s because the Web Standards Project changed everything that had become the norm on the web during its 15+ years of service. Through dedication and developer advocacy, they hoisted the web up from a nest of browser incompatibility and meaningless markup to the standardized and feature-rich application platform most of us know today.

I previously covered what it took to bring CSS to the World Wide Web. This is the other side of that story. It was only through the efforts of many volunteers working tirelessly behind the scenes that CSS ever had a chance to become what it is today. They are the reason we have web standards at all.

Introducing Web Standards

Web standards weren’t even a thing in 1998. There were HTML and CSS specifications and drafts of recommendations that were managed by the W3C, but they had spotty and uneven browser support which made them little more than words on a page. At the time, web designers stood at the precipice of what would soon be known as the Browser Wars, where Netscape and Microsoft raced to implement exclusive features and add-ons in an escalating fight for market share. Rather than stick to any official specification, these browsers forced designers to support either Netscape Navigator or Internet Explorer. And designers were definitely not happy about it.

Supporting both browsers and their competing feature implementations was possible, but it was also difficult and unreliable, like building a house on sand. To help each other along, many developers began joining mailing lists to swap tips and hacks for dealing with sites that needed to look good no matter where it was rendered.

From these mailing lists, a group began to form around an entirely new idea. The problem, this new group realized, wasn’t with the code, but with the browsers that refused to adhere to the codified, open specifications passed down by the W3C. Browsers touted new presentational HTML elements like the <blink> tag, but they were proprietary and provided no layout options. What the web needed was browsers that could follow the standards of the web.

The group decided they needed to step up and push browsers in the right direction. They called themselves the Web Standards Project. And, since the process would require a bit of a sting, they went by WaSP for short.

Launching the Web Standards Project

In August of 1998, WaSP announced their mission to the public on a brand new website: to “support these core standards and encourage browser makers to do the same, thereby ensuring simple, affordable access to Web technologies for all.” Within a few hours, 450 people joined WaSP. In a few months, that number would jump to thousands.

WaSP took what was basically a two-pronged approach. The first was in public, tapping into the groundswell of developer support they had gathered to lobby for better standards support in browsers. Using grassroots tactics and targeted outreach, WaSP would often send its members on “missions” such as sending emails to browsers explaining in great detail their troubles working with a lack of consistent web standards support.

They also published scathing reports that put browsers on blast, highlighting all the ways that Netscape or Internet Explorer failed to add necessary support, even go so far to encourage users to use alternative browsers. It was these reports where the project truly lived up to its acronym. One needs to look no further then a quote from WaSP’s savage takedown of Internet Explorer as an example of its ability to sting:

Quit before the job’s done, and the flamethrower’s the only answer. Because that’s our job. We speak for thousands of Web developers, and through them, millions of Web users.

The second prong of WaSP’s approach included privately reaching out to passionate developers on browser teams. The problem, for big companies like Netscape and Microsoft, wasn’t that engineers were against web standards. Quite the contrary, actually. Many browser engineers believed deeply in WaSP’s mission but were resisted by perceived business interests and red-tape bureaucracy time and time again. As a result, WaSP would often work with browser developers to find the best path forward and advocate on their behalf to the higher-ups when necessary.

Holding it All Together

To help WaSP navigate its way through its missions, reports, and outreach, a Steering Committee was formed. This committee helped set the project’s goals and reached out to the community to gather support. They were the heralds of a better day soon to come, and more than a few influential members would pass through their ranks before the project was over, including: Rachel Cox, Tim Bray, Steve Champeon, Glenn Davis, Glenda Sims, Todd Fahrner, Molly Holzschalg and Aaron Gustafson, among many, many others.

At the top of it all was a project lead who set the tone for the group and gave developers a unified voice. The position was initially held by George Olsen, one of the founders of the project, but was soon picked up by another founding member: Jeffrey Zeldman.

A network of loosely connected satellite groups orbiting around the Steering Committee helped developers and browsers alike understand the importance of web standards. There was, for instance, an Accessibility group that bridged the W3C with browser makers to ensure the web was open and accessible to everyone. Then there was the CSS Samurai, who published reports about CSS support (or, more commonly, lack thereof) in different browsers. They were the ones that devised the Box Acid test and offered guidance to browsers as they worked to expand CSS support. Todd Fahrner, who helped save CSS with doctype switching, counted himself among the CSS Samurai.

Making an Impact

WaSP was huge and growing all the time. Its members were passionate and, little by little, clusters of the community came together to enact change. And that is exactly what happened.

The changes felt kind of small at first but soon they bordered on massive. When Netscape was kicking around the idea of a new rendering engine named Gecko that would include much better standards support across the board, their initial timeline would have taken months to release. But the WaSP swarmed, emailing and reaching out to Netscape to put pressure on them to release Gecko sooner. It worked and, by the next release, Gecko (and better web standards) shipped.

Tantek Çelik was another member of WaSP. The community inspired him to take a stand on web standards at his day job as lead developer of Internet Explorer for Mac. It was through the encouragement and support of WaSP that he and his team released version 5 with full CSS Level 1 support.

Internet Explorer 5 for Mac was released with full CSS Level 1 support

In August of 2001, after years of public reports and private outreach and developer advocacy, the WaSP sting provoked seismic change in Internet Explorer as version 6 released with CSS Level 1 support and the latest HTML features. The upgrades were due in no small part to the work at the Web Standards Project and their work with dedicated members of the browser team. It appeared that standards were beginning to actually win out. The WaSP’s mission may have even been over.

But instead of calling it quits, they shifted tactics a bit.

Teaching Standards to a New Generation

In the early 2000’s, WaSP would radically change its approach to education and developer outreach.

They started with the launch of the Browser Upgrade Campaign which educated users who were coming online for the very first time and knew absolutely nothing about web standards and modern browsers. Site owners were encouraged to add some JavaScript and a banner to their sites to target these users. As a result, those surfing to a site on older versions of standards-compliant browsers, like Firefox or Opera, were greeted by a banner simply directing them to upgrade. Users visiting the site on a really old browser, like pre-IE5 or Netscape 5, would redirect visitors to an entirely new page explaining why upgrading to a modern browser with standards support was in their best interest.

A page from the Browser Upgrade Campaign

WaSP was going to bring the web up to speed, even if they had to do it one person at a time. Perhaps no one articulated this sentiment better than Molly Holzschalg when she wrote “Raise Your Standards” in February 2002. In the article, she broke down what web standards are and what they meant for developers and designers. She celebrated the work that had been done by browsers and the community working to make web standards a thing in the first place.

But, she argued, the web was far from done. It was now time for developers to step up to the plate and assume the responsibility for standards themselves by coding it into all of their sites. She wrote:

The Consortium is fraught with its own internal issues, and its actions—while almost always in the best interests of professional Web authors—are occasionally politicized.

Therefore, as Web authors, we’re personally responsible for making implementation decisions within the framework of a site’s markup needs. It’s our job to administer recommendations to the best of our abilities.

This, however, would not be easy. It would once again require the combined efforts of WaSP members to pull together and teach the web a new way to code. Some began publishing tutorials to their personal blogs or on A List Apart. Others created a standards-based online curriculum for web developers who were new to the field. A few members even formed brand-new task forces to work with popular software tools, like Adobe Dreamweaver, and ensure that standards were supported there as well.

The redesigns of ESPN and Wired, which stood as a testament and example for standards-based designs for years to come, were undertaken in part because members of those teams were inspired by the work that WaSP was doing. They would not have been able to take those crucial first steps if not for the examples and tutorials made freely available to them by gracious WaSP members.

That is why web standards is basically second nature to many web developers today. It’s also why we have such a free spirit of creative exchange in our industry. It all started when WaSP decided to share the correct way of doing things right out in the open.

Looking Past Web Standards

It was this openness that carried WaSP into the late 2010’s. When Holzschlag took over as lead, she advocated for transparency and collaboration between browser makers and the web community. The WaSP, Holzschlag realized, was no longer necessary and could be done from within. For example, she made inroads at Microsoft to help make web standards a top priority on their browser team.

With each subsequent release, browsers began to catch up to the latest standards from the W3C. Browsers like Opera and Firefox actually competed on supporting the latest standards. Google Chrome used web standards as a selling point when it was initially released around the same time. The decade-and-a-half of work by WaSP was paying off. Browser makers were listening to the W3C and the web community, even going so far as to experiment with new standards before they were officially published for recommendation.

In 2013, WaSP posted its farewell announcement and closed up shop for good. It was a difficult decision for those who had fought long and hard for a better, more accessible and more open web, but it was necessary. There are still a number of battlegrounds for the open web but, thanks to the efforts of WaSP, the one for web standards has been won.

Enjoy learning about web history? Jay Hoffmann has a weekly newsletter called The History of the Web you can sign up for here.


A Short History of WaSP and Why Web Standards Matter is a post from CSS-Tricks

New Features Coming Soon in Safari

Here’s a great thread by Ricky Mondello that outlines all of the nifty new features in the latest Safari across macOS and iOS. Some of my favorites include the ability to replace gifs with mp4s, the Payment Request API and support for the Web App Manifest.

Direct Link to Article — Permalink


New Features Coming Soon in Safari is a post from CSS-Tricks

Secure Contexts Everywhere

Anne van Kesteren for Mozilla says:

Effective immediately, all new features that are web-exposed are to be restricted to secure contexts. Web-exposed means that the feature is observable from a web page or server, whether through JavaScript, CSS, HTTP, media formats, etc. A feature can be anything from an extension of an existing IDL-defined object, a new CSS property, a new HTTP response header, to bigger features such as WebVR. In contrast, a new CSS color keyword would likely not be restricted to secure contexts.

In other words, if your site isn’t HTTPS, you won’t get new web tech features. Holy jeepers. The reasoning is the web should be using HTTPS, so this is our way of beating you with a stick if you try to use fancy features without going HTTPS first.

It’ll be fascinating to watch the first major feature drop and if they stick to their word here. The web dev forums of the internet will overflow with WHY DOESN’T grid-gap WORK WITH MY FLEXBOX? (or some likely coming-soon feature) questions and the answer will be: talk to your server team. What if they drop container queries behind this? That would be a hilarious devastating tornado of developer fury.

Direct Link to Article — Permalink


Secure Contexts Everywhere is a post from CSS-Tricks

A Sliding Nightmare: Understanding the Range Input

You may have already seen a bunch of tutorials on how to style the range input. While this is another article on that topic, it’s not about how to get any specific visual result. Instead, it dives into browser inconsistencies, detailing what each does to display that slider on the screen. Understanding this is important because it helps us have a clear idea about whether we can make our slider look and behave consistently across browsers and which styles are necessary to do so.

Looking inside a range input

Before anything else, we need to make sure the browser exposes the DOM inside the range input.

In Chrome, we bring up DevTools, go to Settings, Preferences, Elements and make sure the Show user agent shadow DOM option is enabled.

Series of Chrome screenshots illustrating the steps described above.
Sequence of Chrome screenshots illustrating the steps from above.

In Firefox, we go to about:config and make sure the devtools.inspector.showAllAnonymousContent flag is set to true.

Series of Firefox screenshots illustrating the steps described above.
Sequence of Firefox screenshots illustrating the steps from above.

For a very long time, I was convinced that Edge offers no way of seeing what’s inside such elements. But while messing with it, I discovered that where there’s a will and (and some dumb luck) there’s a way! We need to bring up DevTools, then go to the range input we want to inspect, right click it, select Inspect Element and bam, the DOM Explorer panel now shows the structure of our slider!

Series of Edge screenshots illustrating the steps described above.
Sequence of Edge screenshots illustrating the steps from above.

Apparently, this is a bug. But it’s also immensely useful, so I’m not complaining.

The structure inside

Right from the start, we can see a source for potential problems: we have very different beasts inside for every browser.

In Chrome, at the top of the shadow DOM, we have a div we cannot access anymore. This used to be possible back when /deep/ 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, we have another one for the track and, within the track div, we have a third div for the thumb. These last two are both clearly labeled with an id attribute, but another thing I find strange is that, while we can access the track with ::-webkit-slider-runnable-track and the thumb with ::-webkit-slider-thumb, only the track div has a pseudo attribute with this value.

Chrome screenshot of the structure we have inside a range input.
Inner structure in Chrome.

In Firefox, we also see three div elements inside, only this time they’re not nested – all three of them are siblings. Furthermore, they’re just plain div 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.

Firefox screenshot of the structure we have inside a range input.
Inner structure in Firefox.

We can access the track (first div) with ::-moz-range-track, the progress (second div) with ::-moz-range-progress and the thumb (last div) with ::-moz-range-thumb.

The 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- 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 on the elements between the actual input and its track or the transition on the thumb’s parent.

Edge screenshot of the structure we have inside a range input.
Inner structure in Edge.

Having 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.

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 or filter or even transform 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).

The most efficient way I’ve found to set common styles is by using a Sass mixin because the following won’t work:

input::-webkit-slider-runnable-track, input::-moz-range-track, input::-ms-track { /* common styles */ }

To make it work, we’d need to write it like this:

input::-webkit-slider-runnable-track { /* common styles */ }
input::-moz-range-track { /* common styles */ }
input::-ms-track { /* common styles */ }

But 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.

@mixin track() { /* common styles */ } input { &::-webkit-slider-runnable-track { @include track } &::-moz-range-track { @include track } &::-ms-track { @include track }
}

Note 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.

Initial styles

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.

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 also changes the color and adds a border).

WebKit 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 (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 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.

Note that setting -webkit-appearance: none on the range input 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.

The actual range input element

The first property I’ve thought about checking, box-sizing, happens to have the same value in all browsers – content-box. We can see this by looking up the box-sizing property in the Computed tab in DevTools.

Comparative screenshots of DevTools in the three browsers showing the computed values of box-sizing for a range input.
The box-sizing of the range input, comparative look at all three browsers (from top to bottom: Chrome, Firefox, Edge).

Sadly, 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, border, padding, width, height.

By default, the margin is 2px in Chrome and Edge and 0 .7em in Firefox.

Before we move on, let’s see how we got the values above. The computed length values we get are always px values.

However, 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 was indeed set as a px value.

Screenshot of Chrome DevTools showing where to look for how browser styles were set.
Tracing browser styles in Chrome, the margin case.

Firefox also lets us trace the source of the browser styles in some cases, as shown in the screenshot below:

Screenshot of Firefox DevTools showing where to look for how browser styles were set.
Tracing browser styles in Firefox and how this fails for the margin of our range input.

However, 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:

  1. When changing the font-size on the input or on the html, which entails is was set as an em or rem value.
  2. When changing the viewport, which indicates the value was set using % values or viewport units. This can probably be safely skipped in a lot of cases though.
Gif recording showing how changing the font-size on the range input changes the margin value in Firefox.
Changing the font-size of the range input in Firefox also changes its margin value.

The 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 value depends on anything else.

Gif recording showing how changing the font-size on the range input doesn't change the margin value in Edge.
Changing the font-size of the range input in Edge doesn’t change its margin value.

In any event, this all means margin is a property we need to set explicitly in the input[type='range'] if we want to achieve a consistent look across browsers.

Since we’ve mentioned the font-size, let’s check that as well. Sure enough, this is also inconsistent.

First off, we have 13.3333px 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, it seems to have been set as such and doesn’t depend on the viewport dimensions or on the parent or root font-size.

Screenshot of Chrome DevTools showing the user agent rule where the font-size for inputs is set.
The font-size of the range input in Chrome.

Firefox shows us the same computed value, except this seems to come from setting the font shorthand to -moz-field, which I was first very confused about, especially since background-color is set to -moz-Field, 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 for making the input look like what any input on the current OS looks like.

Screenshot of Firefox DevTools showing how the font-size for inputs is set.
The font-size of the range input in Firefox.

Finally, Edge gives us 16px for its computed value and this seems to be either inherited from its parent or set as 1em, as illustrated by the recording below:

Recording of Edge DevTools showing the computed value of font-size for inputs and how this changes when changing the font-size of the parent.
The font-size of the range input in Edge.

This is important because we often want to set dimensions of sliders and controls (and their components) in general using em 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 units, then having a noticeable font-size difference between browsers here will result in our range input being smaller in some browsers and bigger in others.

For this reason, I always make sure to explicitly set a font-size on the actual slider. Or I might set the font 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.

Before we move on to borders, let’s first see the color property. In Chrome this is rgb(196,196,196) (set as such), which makes it slightly lighter than silver (rgb(192,192,192)/ #c0c0c0), while in Edge and Firefox, the computed value is rgb(0,0,0) (which is solid black). 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.

Comparative screenshots of DevTools in the three browsers showing the computed values of color for a range input.
The color of the range input, comparative look at all three browsers (from top to bottom: Chrome, Firefox, Edge).

The border is set to initial in Chrome, which is equivalent to none medium currentcolor (values for border-style, border-width and border-color). How thick a medium border is exactly depends on the browser, though it’s at least as thick as a thin one everywhere. In Chrome in particular, the computed value we get here is 0.

Screenshot of Chrome DevTools showing how the border for inputs is set.
The border of the range input in Chrome.

In Firefox, we also have a none medium currentcolor value set for the border, though here medium seems to be equivalent to 0.566667px, a value that doesn’t depend on the element or root font-size or on the viewport dimensions.

Screenshot of Firefox DevTools showing how the border for inputs is set.
The border of the range input in Firefox.

We can’t see how everything was set in Edge, but the computed values for border-style and border-width are none and 0 respectively. The border-color changes when we change the color property, which means that, just like in the other browsers, it’s set to currentcolor.

Recording of Edge DevTools showing the computed values of border properties for inputs and how border-color changes when changing the element's color property.
The border of the range input in Edge.

The padding is 0 in both Chrome and Edge.

Comparative screenshots of DevTools in Chrome and Edge browsers showing the computed values of padding for a range input.
The padding of the range input, comparative look at Chrome (top) and Edge (bottom).

However, if we want a pixel-perfect result, then we need to set it explicitly because it’s set to 1px in Firefox.

Screenshot of Firefox DevTools showing how the padding for inputs is set.
The padding of the range input in Firefox.

Now 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/ rgba(0, 0, 0, 0) in Edge and Firefox, but rgb(255,255,255) (solid white) in Chrome.

Comparative screenshots of DevTools in the three browsers showing the computed values of background-color for a range input.
The background-color of the range input, comparative look at all three browsers (from top to bottom: Chrome, Firefox, Edge).

And… finally, let’s look at the dimensions. I’ve saved this for last because here is where things start to get really messy.

Chrome and Edge both give us 129px for the computed value of the width. 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 elements do (which is definitely not the case here) or on the children. There’s also a -webkit-logical-width property taking the same 129px 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 – in other words, it’s the width for horizontal writing-mode and the height for vertical writing-mode.

Gif recording showing how changing the font-size on the range input doesn't change its width value in Chrome.
Changing the font-size of the range input in Chrome doesn’t change its width value.

In any event, it doesn’t depend on the font-size of the input itself or of that of the root element nor on the viewport dimensions in either browser.

Gif recording showing how changing the font-size on the range input doesn't change its width value in Edge.
Changing the font-size of the range input in Edge doesn’t change its width value.

Firefox is the odd one out here, returning a computed value of 160px for the default width. This computed value does however depend on the font-size of the range input – it seems to be 12em.

Gif recording showing how changing the font-size on the range input also changes its width value in Firefox.
Changing the font-size of the range input in Firefox also changes its width value.

In the case of the height, Chrome and Edge again both agree, giving us a computed value of 21px. Just like for the width, I cannot see this being set anywhere in the user agent stylesheet in Chrome DevTools, which normally happens when the height of an element depends on its content.

Gif recording showing how changing the font-size on the range input doesn't change its height value in Chrome.
Changing the font-size of the range input in Chrome doesn’t change its height value.

This value also doesn’t depend on the font-size in either browser.

Gif recording showing how changing the font-size on the range input doesn't change its height value in Edge.
Changing the font-size of the range input in Edge doesn’t change its height value.

Firefox is once again different, giving us 17.3333px as the computed value and, again, this depends on the input‘s font-size – it’s 1.3em.

Gif recording showing how changing the font-size on the range input also changes its height value in Firefox.
Changing the font-size of the range input in Firefox also changes its height value.

But this isn’t worse than the margin case, right? Well, so far, it isn’t! But that’s just about to change because we’re now moving on to the track component.

The range track component

There’s one more possibility regarding the actual input dimensions that we haven’t yet considered: that they’re influenced by those of its components. So let’s explicitly set some dimensions on the track and see whether that influences the size of the slider.

Apparently, in this situation, nothing changes for the actual slider in the case of the width, but we can spot more inconsistencies when it comes to the track width, which, by default, stretches to fill the content-box of the parent input in all three browsers.

In Firefox, if we explicitly set a width, any width on the track, then the track takes this width we give it, expanding outside of its parent slider or shrinking inside, but always staying middle aligned with it. Not bad at all, but, sadly, it turns out Firefox is the only browser that behaves in a sane manner here.

Gif recording showing how changing the width on the track component doesn't influence the width of the range input in Firefox only that of the track. Furthermore, the track and the actual range input are always middle aligned horizontally.
Explicitly setting a width on the track changes the width of the track in Firefox, but not that of the parent slider.

In Chrome, the track width we set is completely ignored and it looks like there’s no sane way of making it have a value that doesn’t depend on that of the parent slider.

Gif recording showing how changing the width on the track component doesn't do anything in Chrome.
Changing the width of the track doesn’t do anything in Chrome (computed value remains 129px).

As for insane ways, using transform: scaleX(factor) seems to be the only way to make the track wider or narrower than its parent slider. Do note doing this also causes quite a few side effects. The thumb is scaled horizontally as well and its motion is limited to the scaled down track in Chrome and Edge (as the thumb is a child of the track in these browsers), but not in Firefox, where its size is preserved and its motion is still limited to the input, not the scaled down track (since the track and thumb are siblings here). Any lateral padding, border or margin on the track is also going to be scaled.

Moving on to Edge, the track again takes any width we set.

Gif recording showing how Edge allows us to change the width of the track without changing that of the parent slider.
Edge also allows us to set a track width that’s different from that of the parent slider.

This is not the same situation as Firefox however. While setting a width greater than that of the parent slider on the track makes it expand outside, the two are not middle aligned. Instead, the left border limit of the track is left aligned with the left content limit of its range input parent. This alignment inconsistency on its own wouldn’t be that much of a problem – a margin-left set only on ::-ms-track could fix it.

However, everything outside of the parent slider’s content-box gets cut out in Edge. This is not equivalent to having overflow set to hidden on the actual input, which would cut out everything outside the padding-box, not content-box. Therefore, it cannot be fixed by setting overflow: visible on the slider.

This clipping is caused by the elements between the input and the track having overflow: hidden, but, since we cannot access these, we also cannot fix this problem. Setting everything such that no component (including its box-shadow) goes outside the content-box of the range is an option in some cases, but not always.

For the height, Firefox behaves in a similar manner it did for the width. The track expands or shrinks vertically to the height we set without affecting the parent slider and always staying middle aligned to it vertically.

Gif recording showing how changing the height on the track component doesn't influence the height of the range input in Firefox only that of the track. Furthermore, the track and the actual range input are always middle aligned vertically.
Explicitly setting a height on the track changes the height of the track in Firefox, but not that of the parent slider.

The default value for this height with no styles set on the actual input or track is .2em.

Gif recording showing how changing the font-size on the track changes its computed height in Firefox.
Changing the font-size on the track changes its computed height in Firefox.

Unlike in the case of the width, Chrome allows the track to take the height we set and, if we’re not using a % value here, it also makes the content-box of the parent slider expand or shrink such that the border-box of the track perfectly fits in it. When using a % value, the actual slider and the track are middle aligned vertically.

Gif recording showing how changing the height on the track component doesn't influence the height of the range input in Chrome if the value we set is a % value. Otherwise, the track expands or shrinks such that the track perfectly fits in. Furthermore, in the % case, the track and the actual range input are always middle-aligned vertically.
Explicitly setting a height on the track in % changes the height of the track in Chrome, but not that of the parent slider. Using other units, the actual range input expands or shrinks vertically such that the track perfectly fits inside.

The computed value we get for the height without setting any custom styles is the same as for the slider and doesn’t change with the font-size.

Gif recording showing how changing the font-size on the track doesn't change its computed height in Chrome.
Changing the font-size on the track doesn’t change its computed height in Chrome.

What about Edge? Well, we can change the height of the track independently of that of the parent slider and they both stay middle aligned vertically, but all of this is only as long as the track height we set is smaller than the initial height of the actual input. Above that, the track’s computed height is always equal to that of the parent range.

Gif recording showing how changing the height on the track component doesn't influence the height of the range input in Edge. The track and the actual range input are always middle aligned vertically. However, the height of the track is limited by that of the parent slider.
Explicitly setting a height on the track in Edge doesn’t change the height of the parent slider and the two are middle aligned. However, the height of the track is limited by that of the actual input.

The initial track height is 11px and this value doesn’t depend on the font-size or on the viewport.

Gif recording showing how changing the font-size on the track doesn't change its computed height in Edge.
Changing the font-size on the track doesn’t change its computed height in Edge.

Moving on to something less mindbending, we have box-sizing. This is border-box in Chrome and content-box in Edge and Firefox so, if we’re going to have a non-zero border or padding, then box-sizing is a property we need to explicitly set in order to even things out.

Comparative screenshots of DevTools in the three browsers showing the computed values of box-sizing for the track.
The box-sizing of the track, comparative look at all three browsers (from top to bottom: Chrome, Firefox, Edge).

The default track margin and padding are both 0 in all three browsers – finally, an oasis of consistency!

Comparative screenshots of DevTools in the three browsers showing the computed values of margin for the track.
The box-sizing of the track, comparative look at all three browsers (from top to bottom: Chrome, Firefox, Edge).

The values for the color property can be inherited from the parent slider in all three browsers.

Comparative screenshots of DevTools in Chrome and Firefox browsers showing the computed values of color for the track.
The color of the track, comparative look at Chrome (top) and Firefox (bottom).

Even so, Edge is the odd one here, changing it to white, though setting it to initial changes it to black, which is the value we have for the actual input.

Resetting the color to initial in Edge.
Resetting the color to initial in Edge.

Setting -webkit-appearance: none on the actual input in Edge makes the computed value of the color on the track transparent (if we haven’t explicitly set a color value ourselves). Also, once we add a background on the track, the computed track color suddenly changes to black.

Adding a background on the track in Edge changes its computed color from white to black.
Unexpected consequence of adding a background track in Edge.

To a certain extent, the ability to inherit the color property is useful for theming, though inheriting custom properties can do a lot more here. For example, consider we want to use a silver for secondary things and an orange for what we want highlighted. We can define two CSS variables on the body and then use them across the page, even inside our range inputs.

body { --fading: #bbb; --impact: #f90
} h2 { border-bottom: solid .125em var(--impact) } h6 { color: var(--fading) } [type='range']:focus { box-shadow: 0 0 2px var(--impact) } @mixin track() { background: var(--fading) } @mixin thumb() { background: var(--impact) }

Sadly, while this works in Chrome and Firefox, Edge doesn’t currently allow custom properties on the range inputto be inherited down to its components.

Screenshots of the expected result (and what we get in Chrome and Firefox) vs. the result we get in Edge (neither the thumb or the track show up)
Expected result (left) vs. result in Edge (right), where no track or thumb show up (live demo).

By default, there is no border on the track in Chrome or Firefox (border-width is 0 and border-style is none).

Comparative screenshots of DevTools in Chrome and Firefox browsers showing the computed values of border for the track.
The border of the track, comparative look at Chrome (top) and Firefox (bottom).

Edge has no border on the track if we have no background set on the actual input and no background set on the track itself. However, once that changes, we get a thin (1px) black track border.

Adding a background on the track or actual input in Edge gives the track a solid 1px black border.
Another unexpected consequence of adding a track or parent slider background in Edge.

The default background-color is shown to be inherited as white, but then somehow we get a computed value of rgba(0,0,0,0) (transparent) in Chrome (both before and after -webkit-appearance: none). This also makes me wonder how come we can see the track before, since there’s no background-color or background-image to give us anything visible. Firefox gives us a computed value of rgb(153,153,153) (#999) and Edge transparent (even though we might initially think it’s some kind of silver, that is not the background of the ::-ms-track element – more on that a bit later).

Comparative screenshots of DevTools in the three browsers showing the computed values of background-color for the track.
The background-color of the track, comparative look at all three browsers (from top to bottom: Chrome, Firefox, Edge).

The range thumb component

Ready for the most annoying inconsistency yet? The thumb moves within the limits of the track’s content-box in Chrome and within the limits of the actual input‘s content-box in Firefox and Edge, even when we make the track longer or shorter than the input (Chrome doesn’t allow this, forcing the track’s border-box to fit the slider’s content-box horizontally).

The way Chrome behaves is illustrated below:

Chrome only moves the thumb within the left and right limits of the track's content-box.
Recording of the thumb motion in Chrome from one end of the slider to the other.

The padding is transparent, while the content-box and the border are semitransparent. We’ve used orange for the actual slider, red for the track and purple for the thumb.

For Firefox, things are a bit different:

Firefox moves the thumb within the left and right limits of the actual range input's content-box.
Recording of the thumb motion in Firefox from one end of the slider to the other (the three cases from top to bottom: the border-box of the track perfectly fits the content-box of the slider horizontally, it’s longer and it’s shorter).

In Chrome, the thumb is the child of the track, while in Firefox it’s its sibling, so, looking at it this way, it makes sense that Chrome would move the thumb within the limits of the track’s content-box and Firefox would move it within the limits of the slider’s content-box. However, the thumb is inside the track in Edge too and it still moves within the limits of the slider’s content-box.

Animated gif. Shows how Edge moves the thumb within the left and right limits of the actual range input's content-box.
Recording of the thumb motion in Edge from one end of the slider to the other (the three cases from top to bottom: the border-box of the track perfectly fits the content-box of the slider horizontally, it’s longer and it’s shorter).

While this looks very strange at first, it’s because Edge forces the position of the track to static and we cannot change that, even if we set it to relative with !important.

Animated gif. Recording of the following steps: 1) checking the computed value of the position property on the track in Edge DevTools - it's static 2) setting ::-ms-track { position: relative } 3) checking the computed value again - it's still static 4) adding !important to the rule previously set on the track 5) checking the computed value a third time - annoyingly, it's still static!
Trying (and failing) to change the value of the position property on the track in Edge.

This means we may style our slider exactly the same for all browsers, but if its content-box doesn’t coincide to that of its track horizontally (so if we have a non-zero lateral padding or border on the track), it won’t move within the same limits in all browsers.

Furthermore, if we scale the track horizontally, then Chrome and Firefox behave as they did before, the thumb moving within the limits of the now scaled track’s content-box in Chrome and within the limits of the actual input‘s content-box in Firefox. However, Edge makes the thumb move within an interval whose width equals that of the track’s border-box, but starts from the left limit of the track’s padding-box, which is probably explained by the fact that the transform property creates a stacking context.

Edge moves the thumb within an interval equal to the scaled track's border-box, starting from the left limit of the padding-box
Recording of the thumb motion in Edge when the track is scaled horizontally.

Vertically, the thumb is middle-aligned to the track in Firefox, seemingly middle-aligned in Edge, though I’ve been getting very confusing different results over multiple tests of the same situation, and the top of its border-box is aligned to the top of the track’s content-box in Chrome once we’ve set -webkit-appearance: none on the actual input and on the thumb so that we can style the slider.

While the Chrome decision seems weird at first, is annoying in most cases and lately has even contributed to breaking things in… Edge (but more about that in a moment), there is some logic behind it. By default, the height of the track in Chrome is determined by that of the thumb and if we look at things this way, the top alignment doesn’t seem like complete insanity anymore.

However, we often want a thumb that’s bigger than the track’s height and is middle aligned to the track. We can correct the Chrome alignment with margin-top in the styles we set on the ::-webkit-slider-thumb pseudo.

Unfortunately, this way we’re breaking the vertical alignment in Edge. This is because Edge now applies the styles set via ::-webkit-slider-thumb as well. At least we have the option of resetting margin-top to 0 in the styles we set on ::-ms-thumb. The demo below shows a very simple example of this in action.

See the Pen by thebabydino (@thebabydino) on CodePen.

Just like in the case of the track, the value of the box-sizing property is border-box in Chrome and content-box in Edge and Firefox, so, for consistent results across browsers, we need to set it explicitly if we want to have a non-zero border or padding on the thumb.

The margin and padding are both 0 by default in all three browsers.

After setting -webkit-appearance: none on both the slider and the thumb (setting it on just one of the two doesn’t change anything), the dimensions of the thumb are reset from 10x21 (dimensions that don’t depend on the font-size) to 129x0 in Chrome. The height of the track and actual slider also get reset to 0, since they depend on that of their content (the thumb inside, whose height has become 0).

Animated gif. Shows Chrome DevTools with the thumb selected. Changing the font-size on the thumb doesn't change its dimensions. Setting -webkit-appearance: none on both the thumb and the actual slider resets its dimensions to 129x0
The thumb box model in Chrome.

This is also why explicitly setting a height on the thumb makes the track take the same height.

According to Chrome DevTools, there is no border in either case, even though, before setting -webkit-appearance: none, it sure looks like there is one.

Screenshot. Before setting -webkit-appearance:none, it looks like there is a border on the thumb, even though Chrome DevTools says there isn't.
How the slider looks in Chrome before setting -webkit-appearance: none.

If that’s not a border, it might be an outline or a box-shadow with no blur and a positive spread. But, according to Chrome DevTools, we don’t have an outline, nor box-shadow on the thumb.

Screenshot. The computed value for outline in Chrome DevTools is none 0px rgb(196, 196, 196), while that for box-shadow is none.
Computed values for outline and box-shadow in Chrome DevTools.

Setting -webkit-appearance: none in Edge makes the thumb dimensions go from 11x11 (values that don’t depend on the font-size) to 0x0. Explicitly setting a height on the thumb makes the track take the initial height (11px).

Animated gif. Shows Edge DevTools with the thumb selected. Changing the font-size on the thumb doesn't change its dimensions. Setting -webkit-appearance: none on both the thumb and the actual slider resets its dimensions to 0x0
The thumb box model in Edge.

In Edge, there’s initially no border on the thumb. However, after setting a background on either the actual range input or any of its components, we suddenly get a solid 1px white lateral one (left and right, but not top and bottom), which visually turns to black in the :active state (even though Edge DevTools doesn’t seem to notice that). Setting -webkit-appearance: none removes the border-width.

Animated gif. Shows Edge DevTools with the thumb selected. There is originally no border, but setting a background on either the slider or its components makes the lateral borders solid 1px white ones. Setting -webkit-appearance: none on both the thumb and the actual slider removes this border (as well as making both thumb dimensions 0).
The thumb border in Edge.

In Firefox, without setting a property like background on the range input or its components, the dimensions of the thumb are 1.666x3.333 and, in this case, they don’t change with the font-size. However, if we set something like background: transparent on the slider (or any background value on its components), then both the width and height of the thumb become 1em.

Animated gif. Shows Firefox DevTools with the thumb selected. Changing the font-size on the thumb doesn't change initially its dimensions. However, after setting a background on the actual input, the thumb dimensions become equal to the font-size (1em).
The thumb box model in Firefox.

In Firefox, if we are to believe what we see in DevTools, we initially have a solid thick grey (rgb(153, 153, 153)) border.

Screenshot. Shows Firefox DevTools displaying the computed values for the slider thumb border.
The thumb border in Firefox DevTools.

Visually however, I can’t spot this thick grey border anywhere.

Screenshot of the slider in its initial state in Firefox, before setting a background on it or on any of its components. I cannot see any border on the thumb, even Firefox DevTools says there is a pretty thick one.
How the slider looks initially in Firefox, before setting a background on it or on any of its components.

After setting a background on the actual range input or one of its components, the thumb border actually becomes visually detectable and it seems to be .1em.

Animated gif. Shows Firefox DevTools with the thumb selected. In DevTools we originally see a thickish grey border, with a different width on every side, but setting a background on either the slider or its components makes this border thinner an uniform around the thumb. Its width varies with the font-size and it seems to be .1em.
The thumb border in Firefox.

In Chrome and in Edge, the border-radius is always 0.

Screenshots. Top: screenshot of Chrome DevTools showing the computed value for the thumb's border-radius is 0. Bottom: screenshot of Edge DevTools showing the computed value for the thumb's border-radius is 0.
The thumb border-radius in Chrome (top) and Edge (bottom).

In Firefox however, we have a .5em value for this property, both before and after setting a background on the range input or on its components, even though the initial shape of the thumb doesn’t look like a rectangle with rounded corners.

Animated gif. Shows Firefox DevTools with the thumb selected. In DevTools, we change the font-size on the thumb and, from the way the computed border-radius value changes, we get that it's set to .5em.
The thumb border-radius in Firefox.

The strange initial shape of the thumb in Firefox has made me wonder whether it doesn’t have a clip-path set, but that’s not the case according to DevTools.

Screenshot. Shows Firefox DevTools with the thumb selected. The computed value for the clip-path property on the thumb is none.
The thumb clip-path in Firefox.

More likely, the thumb shape is due to the -moz-field setting, though, at least on Windows 10, this doesn’t make it look like every other slider.

Screenshots. The initial appearance of the slider in Firefox vs. the appearance of a native Windows slider.
Initial appearance of slider in Firefox vs. appearance of a native Windows 10 slider.

The thumb’s background-color is reported as being rgba(0, 0, 0, 0) (transparent) by Chrome DevTools, even though it looks grey before setting -webkit-appearance: none. We also don’t seem to have a background-image that could explain the gradient or the lines on the thumb before setting -webkit-appearance: none. Firefox DevTools reports it as being rgb(240, 240, 240), even though it looks blue as long as we don’t have a background explicitly set on the actual range input or on any of its components.

Screenshots. Top: screenshot of Chrome DevTools showing the computed value for background-color on the thumb is rgba(0, 0, 0, 0) and the computed value for background-image is none. Bottom: screenshot of Firefox DevTools showing the computed value for background-color on the thumb is rgb(240, 240, 240).
The thumb background-color in Chrome (top) and Firefox (bottom).

In Edge, the background-color is rgb(33, 33, 33) before setting -webkit-appearance: none and transparent after.

Animated gif. Shows Edge DevTools with the thumb selected. The computed value for the thumb's background-color is rgb(33, 33, 33). In DevTools, we set -webkit-appearance: none on the actual slider and on the thumb. The computed value for the thumb's background-color becomes transparent.
The thumb background-color in Edge.

The range progress (fill) component

We only have dedicated pseudo-elements for this in Firefox (::-moz-range-progress) and in Edge (::-ms-fill-lower). Note that this element is a sibling of the track in Firefox and a descendant in Edge. This means that it’s sized relative to the actual input in Firefox, but relative to the track in Edge.

In order to better understand this, consider that the track’s border-box perfectly fits horizontally within the slider’s content-box and that the track has both a border and a padding.

In Firefox, the left limit of the border-box of the progress component always coincides with the left limit of the slider’s content-box. When the current slider value is its minimum value, the right limit of the border-box of our progress also coincides with the left limit of the slider’s content-box. When the current slider value is its maximum value, the right limit of the border-box of our progress coincides with the right limit of the slider’s content-box.

This means the width of the border-box of our progress goes from 0 to the width of the slider’s content-box. In general, when the thumb is at x% of the distance between the two limit value, the width of the border-box for our progress is x% of that of the slider’s content-box.

This is shown in the recording below. The padding area is always transparent, while the border area and content-box are semitransparent (orange for the actual input, red for the track, grey for the progress and purple for the thumb).

Animated gif. Shows the slider in Firefox with the thumb at the minimum value. The width of the border-box of the progress component is 0 in this case. We drag the thumb to the maximum slider value. The width of the border-box of the progress component equals that of the slider's content-box in this case.
How the width of the ::-moz-range-progress component changes in Firefox.

In Edge however, the left limit of the fill’s border-box always coincides with the left limit of the track’s content-box while the right limit of the fill’s border-box always coincides with the vertical line that splits the thumb’s border-box into two equal halves. This means that when the current slider value is its minimum value, the right limit of the fill’s border-box is half the thumb’s border-box to the right of the left limit of the track’s content-box. And when the current slider value is its maximum value, the right limit of the fill’s border-box is half the thumb’s border-box to the left of the right limit of the track’s content-box.

This means the width of the border-box of our progress goes from half the width of the thumb’s border-box minus the track’s left border and padding to the width of the track’s content-box plus the track’s right padding and border minus half the width of the thumb’s border-box. In general, when the thumb is at x% of the distance between the two limit value, the width of the border-box for our progress is its minimum width plus x% of the difference between its maximum and its minimum width.

This is all illustrated by the following recording of this live demo you can play with:

Animated gif. Shows the slider in Edge with the thumb at the minimum value. The width of the border-box of the progress component is half the width of the thumb's border-box minus the track's left border and padding in this case. We drag the thumb to the maximum slider value. The width of the border-box of the progress component equals that of the track's content-box plus the track's right padding and border minus half the width of the thumb's border-box.
How the width of the ::-ms-fill-lower component changes in Edge.

While the description of the Edge approach above might make it seem more complicated, I’ve come to the conclusion that this is the best way to vary the width of this component as the Firefox approach may cause some issues.

For example, consider the case when we have no border or padding on the track for cross browser consistency and the height of the both the fill’s and thumb’s border-box equal to that of the track. Furthermore, the thumb is a disc (border-radius: 50%).

In Edge, all is fine:

Animated gif illustrating how the case described above works in Edge using a slider with a grey track and orange progress.
How our example works in Edge.

But in Firefox, things look awkward (live demo):

Animated gif illustrating how the case described above works in Firefox using a slider with a grey track and orange progress.
How our example works in Firefox.

The good news is that we don’t have other annoying and hard to get around inconsistencies in the case of this component.

box-sizing has the same computed value in both browsers – content-box.

Screenshot. Top half shows Firefox DevTools with the progress component selected. The computed value for box-sizing is shown to be content-box. Bottom half shows Edge DevTools with the lower fill component selected. The computed value for box-sizing is shown to be content-box in this case too.
The computed value for box-sizing in the case of the progress (fill) component: Firefox (top) and Edge (bottom).

In Firefox, the height of the progress is .2em, while the padding, border and margin are all 0.

Animated gif. Shows Firefox DevTools with the progress component selected. Changing the font-size on this component also changes its height, allowing us to see it was set as .2em.
The height of the progress in Firefox.

In Edge, the fill’s height is equal to that of the track’s content-box, with the padding, border and margin all being 0, just like in Firefox.

Animated gif. Shows Edge DevTools with the fill component selected. The height of the fill is the same as that of the track's content-box. We set box-sizing: border-box on the track and give it a vertical padding to check this. The height of the fill shrinks accordingly.
The height of the fill in Edge.

Initially, the background of this element is rgba(0, 0, 0, 0) (transparent, which is why we don’t see it at first) in Firefox and rgb(0, 120, 115) in Edge.

Screenshot. Top half shows Firefox DevTools with the progress selected. The computed value for the background-color of the progress is rgba(0, 0, 0, 0). Bottom half shows Edge DevTools with the lower fill selected. The computed value for the fill's background-color is rgb(0, 120, 115).
The background-color of the progress (fill) in Firefox (top) and Edge (bottom).

In both cases, the computed value of the color property is rgb(0, 0, 0) (solid black).

Screenshot. Top half shows Firefox DevTools with the progress component selected. The computed value for color is shown to be rgb(0, 0, 0). Bottom half shows Edge DevTools with the lower fill component selected. The computed value for color is shown to be rgb(0, 0, 0) in this case too.
The computed value for color in the case of the progress (fill) component: Firefox (top) and Edge (bottom).

WebKit browsers don’t provide such a component and, since we don’t have a way of accessing and using a track’s ::before or ::after pseudos anymore, our only option of emulating this remains layering an extra, non-repeating background on top of the track’s existing one for these browsers and making the size of this extra layer along the x axis depend depend on the current value of the range input.

The simplest way of doing this nowadays is by using a current value --val CSS variable, which holds the slider’s current value. We update this variable every time the slider’s value changes and we make the background-size of this top layer a calc() value depending on --val. This way, we don’t have to recompute anything when the value of the range input changes – our calc() value is dynamic, so updating the --val variable is enough (not just for this background-size, but also for other styles that may depend on it as well).

See the Pen by thebabydino (@thebabydino) on CodePen.

Also doing this for Firefox is an option if the way ::-moz-range-progress increases doesn’t look good for our particular use case.

Edge also provides a ::-ms-fill-upper which is basically the complementary of the lower one and it’s the silver background of this pseudo-element that we initially see to the right of the thumb, not that of the track (the track is transparent).

Tick marks and labels

Edge is the only browser that shows tick marks by default. They’re shown on the track, delimiting two, five, ten, twenty sections, the exact number depending initially on the track width. The only style we can change for these tick marks is the color property as this is inherited from the track (so setting color: transparent on the track removes the initial tick marks in Edge).

Screenshot. Shows Edge DevTools with the SVG group containing the tick lines selected. Unfortunately, I cannot access this group, its children, its SVG parent or the SVG container to modify their styles. I can only access the track (which is the SVG container's parent) via ::-ms-track. Since the color property is inherited and the tick lines use currentColor as the stroke value, changing the color on the track also changes the stroke of the tick lines.
The structure that generates the initial tick marks on the track in Edge.

The spec says that tick marks and labels can be added by linking a datalist element, for whose option children we may specify a label attribute if we want that particular tick mark to also have a label.

Unfortunately, though not at all surprising anymore at this point, browsers have a mind of their own here too. Firefox doesn’t show anything – no tick marks, no labels. Chrome shows the tick marks, but only allows us to control their position along the slider with the option values. It doesn’t allow us to style them in any way and it doesn’t show any labels.

Screenshot. Shows the range input with the tick marks generated in Chrome when adding a datalist.
Tick marks in Chrome.

Also, setting -webkit-appearance: none on the actual slider (which is something that we need to to in order to be able to style it) makes these tick marks disappear.

Edge joins the club and doesn’t show any labels either and it doesn’t allow much control over the look of the ticks either. While adding the datalist allows us to control which tick marks are shown where on the track, we cannot style them beyond changing the color property on the track component.

Screenshot. Shows the range input with the tick marks generated in Edge when adding a datalist.
Tick marks in Edge.

In Edge, we also have ::-ms-ticks-before and ::-ms-ticks-after pseudo-elements. These are pretty much what they sound like – tick marks before and after the track. However, I’m having a hard time understanding how they really work.

They’re hidden by display: none, so changing this property to block makes them visible if we also explicitly set a slider height, even though doing this does not change their own height.

Animated gif. Illustrates the steps above to make the tick marks created by ::-ms-ticks-after visible.
How to make tick marks crested by ::-ms-ticks-after visible in Edge.

Beyond that, we can set properties like margin, padding, height, background, color in order to control their look. However, I have no idea how to control the thickness of individual ticks, how to give individual ticks gradient backgrounds or how to make some of them major and some minor.

So, at the end of the day, our best option if we want a nice cross-browser result remains using repeating-linear-gradient for the ticks and the label element for the values corresponding to these ticks.

See the Pen by thebabydino (@thebabydino) on CodePen.

Tooltip/ current value display

Edge is the only browser that provides a tooltip via ::-ms-tooltip, but this doesn’t show up in the DOM, cannot really be styled (we can only choose to hide it by setting display: none on it) and can only display integer values, so it’s completely useless for a range input between let’s say .1 and .4 – all the values it displays are 0!

Animated gif. Dragging the thumb in Edge results in the tooltip displaying always 0 if both the minimum and the maximum are subunitary.
::-ms-tooltip when range limits are both subunitary.

So our best bet is to just hide this and use the output element for all browsers, again taking advantage of the possibility of storing the current slider value into a --val variable and then using a calc() value depending on this variable for the position.

See the Pen by thebabydino (@thebabydino) on CodePen.

Orientation

The good news is that every browser allows us to create vertical sliders. The bad news is, as you may have guessed… every browser provides a different way of doing this, none of which is the one presented in the spec (setting a width smaller than the height on the range input). WebKit browsers have opted for -webkit-appearance: slider-vertical, Edge for writing-mode: bt-lr, while Firefox controls this via an orient attribute with a value of 'vertical'.

The really bad news is that, for WebKit browsers, making a slider vertical this way leaves us unable to set any custom styles on it (as setting custom styles requires a value of none for -webkit-appearance).

Our best option is to just style our range input as a horizontal one and then rotate it with a CSS transform.

See the Pen by thebabydino (@thebabydino) on CodePen.


A Sliding Nightmare: Understanding the Range Input is a post from CSS-Tricks