HTML5 and CSS3 Feature Detection with Modernizr and Beyond

HTML5 and CSS3 Feature detection with Modernizr and Beyond

Table of Contents

What is feature detection? How can you use it in web development? These and many more questions are the subject of this article. First, you will learn about the current champion among feature detection JavaScript libraries. Then, you will explore the interesting future of CSS-based feature detection. In-between, you will also learn about some practices from the dark ages of web development. My goal for this article is simple. Create great experience regardless of what browser your users want to use. Without further ado, let’s begin.

Getting started with feature detection

One of the first things that come to mind when we talk about feature detection is probably Modernizr. This useful JavaScript library has been around for some time. Unless I’m wrong, this feature detection library has been helping web designers and developers since 2009. Since then, Modernizr gone a long way from small library to one of the most known web design tools. So, what exactly is this JavaScript library called Modernizr?

What is Modernizr?

Modernizr is small JavaScript library that helps web designers and developers make our work easier. It helps us automatically detect available features and web technologies in user’s browsers. It also allows us to create and run various test for these features and technologies. As a result, we can use this feature detection to create customized user’s experiences based on the capabilities of the browser. We don’t have to blacklist browsers or use approaches like graceful degradation.

The fact is that Modernizr gives you significant amount of knowledge about users and their browsers. Still, it is still up to you to decide if you take advantage of this or not. Use Modernizr and feature detection to check for new HTML5 and CSS3 features. Then, prepare for situations when browser can use these features and when not. Other options? You can also stick to good old and tested stuff. Or, you can implement these features and ignore browsers that can’t handle them.

I can’t recommend any of these two options. The first one leads to deterioration of your skills and lowers your attractiveness on market. The second one does disservice to your clients. Clients put their trust in you. They pay you to do the best job. This means creating usable and website. Second option doesn’t meet this condition. Anyway, let’s take a moment and look at how web development looked like before Modernizr.

The dark ages of web development

Let’s take a look at how web development before Modernizr came on the stage. Back then, the common practice of developers was to do one of two things. First, you could write your HTML and CSS and hope that it will work. The problem is that this is not the best way to create the same experience across major browsers and devices. What’s more, in these dark ages of web design and development IE was still quite popular. Many jokes about IE probably originated at this time. The second option was to use a little it (or more) of JavaScript code for sniffing.

Web development and UA sniffing

You wrote a bunch of JavaScript if statements and detect various user agents (browsers). This also meant that you had a number of “versions” of your website. Then, depending on user’s browser, you served one of these “versions” of the website. This practice of detecting user agents is often called “UA sniffing”. For example, if user was using Chrome you used version A, if Firefox version B. In case of IE? Let’s not go there. Let me give you one example of this script for UA sniffing. I was using from time to time as well.

Code:

// Example of UA sniffing code
if ((navigator.userAgent.indexOf('Opera') || navigator.userAgent.indexOf('OPR')) != -1 ) {
 console.log('Browser is Opera');
} else if (navigator.userAgent.indexOf('Chrome') != -1 ) {
 console.log('Browser is Chrome');
} else if (navigator.userAgent.indexOf('Safari') != -1) {
 console.log('Browser is Safari');
} else if (navigator.userAgent.indexOf('Firefox') != -1 ) {
 console.log('Browser is Firefox');
} else if ((navigator.userAgent.indexOf('MSIE') != -1 ) || (!!document.documentMode == true )) {
 console.log('Browser is IE'); 
} else {
 console.log('Browser is unknown');
}

Code snippets such as the one in example may look useful. However, I don’t think that this should be considered a good practice. And, I think the same about blacklisting specific browsers. I believe that users should not be discriminated due to their choice of browser. Another consequence of this practice was also growing debate around whether website should look the same in every browser. There is even a website for answering this question.

The dark ages of web development and feature testing

Whether you agreed with the answer was one thing. Whole different one was whether your client or employee agreed with it as well. The fact was that she was the one who paid you. Therefore, if she decided that the website has to look completely the same, you had no choice. Actually, you had two choices. Your first option was to swallow this sour pill and get to work. The relative upside was that the biggest troublemaker was usually IE. So, you had to create “only” two versions.

Your second option was to reject the job. This was the less attractive option. You probably decided to go with the first one. The result? You created the first coded version of the website. This version worked flawlessly on the browser you used for development. Next, you moved to the first browser on the list of the major browsers. What if you found some bug while testing the website on this browser? First, you created a fix and tested it on that browser.

The second step was to switch back to the first browser and test your fix. You had to make sure your fix didn’t break anything. You repeated this cycle over and over again until you tested the website on all major browsers. In other words, you had to go through this cycle in Chrome, Firefox, IE, Opera and Safari. This approach was painfully slow. However, it was necessary if you wanted to make sure that the website will work. Otherwise, it could happen that your client contacted you because she found some bug.

Consequences of failed feature testing

If this situation happened, you had a serious problem. When your client find a bug it means one thing. You didn’t cover everything in your work. Sure, nobody is perfect and we all make some mistake here and there. Still, we are being paid for doing professional work. This means that we should test everything and test it thoroughly. No detail should be too small. When our clients have to check our work, we will immediately start to lose our face.

When we lose our face, it means that our reputation as professionals is doomed. As Warren Buffett says, “it takes 20 years to build a reputation and five minutes to ruin it. If you think about that, you’ll do things differently.” As the quote says, it is not impossible to rebuild your reputation. The problem is that it is time-consuming and not necessary. When you do your job as your life depends on it, you will do the best. You will do what it takes to sustain and even level up your reputation.

I know that this may look like a sidetrack from our today’s subject. Yet, I think that it was important to mention this. My goal was to emphasize how difficult feature testing was before just a couple of years ago. We often take many things and tools for granted. We should remember that there were times when these tools didn’t exist. Now, let’s get back to feature detection and Modernizr.

Getting started with Modernizr

With the advent of tools or feature detection such as Modernizr, browser sniffing is no longer necessary. We don’t have to use scripts full of navigator.userAgent to detect the browsers. Knowing the name of the browser is not so useful anymore. Instead, we can simply test the browser for specific feature. How can we use Modernizr to test for available features? There are two ways to use this JavaScript library. Both require implementing Modernizr into your project.

How to choose the right build

When you visit Modernizr website, you have two options. Your first option is to create and download your own custom build. Your second option is to use premade development build. This build will test the browser for all default features. Which option is a better choice will depend on a number of conditions. First, do you want to implement the library in production? In other words, do you want to use Modernizr on live website? If so, I would suggest using custom build.

Second, do you know which specific features do you want to use in the project? If your answer is “yes”, I would again suggest using custom build. Third, are you limited by file size? In that case, custom build is again the winner. When I think about it, I would always recommend using custom build. There are a number of reason for it. The first reason is that every detect or feature will increase size of the library. Bigger files will have some influence on bandwidth.

I understand that, nowadays, you no longer have to count every byte or kilobyte. Internet connection we now have in many areas of the world is advanced enough. This applies also to mobile devices which also made a huge leap. Still, this should not be an excuse or argument to use more resources than is necessary. The second reason is that every detect requires Modernizr to run another test. Again, I understand that a few additional tests will not make website’s loading visible slower. Still, we shouldn’t make it more performance heavy than is absolutely necessary.

Let me summarize this by saying that custom build is always a better option. It doesn’t matter how much space and money your client gave you. Your performance budget also doesn’t matter. Choose custom build.

What if

Before we move on, we should also consider one rare situation. What if you are not sure about what features will use need to use in the project? First, let me tell you that this is very unlikely to happen. You should never start working on a project before you have the idea about how you realize it. Wait. Let me rephrase it. You should never start working on a client project. Side projects belong to a little bit different area. Along with these reasons, side project are great for experimentation and learning new things.

There is a problem with both these topics. You often don’t know what shape will the result have. Many side projects start with only some more or less clear initial idea. What about the rest of it? You will figure it out on your way. Therefore, in case of this rare example, development build might be a better choice. Now, let’s go back to the client projects and make one thing clear. Don’t start working on the project without having a plan to finish it.

There is at least one solid reason for following this advice. You need to know the plan in order to estimate the time necessary to finish the project. Let me give you one example to illustrate what I mean. Imagine that you would want to travel to your friend. Logically, your friend will ask you when he can expect you. You can’t tell him exact date if you don’t know how will you get there. You need to know what road to take. Otherwise, your estimate will be only desperate guess.

The same principle applies to your client projects. You need to know how to finish it to create accurate estimate. Conclusion? There is no “what if” situation.

Modernizr the easy way

I mentioned that there are two ways to use Modernizr. Let’ discuss the easier as first. Every time Modernizr runs, it appends a number of CSS classes to html element. There is one class for each feature you want to test. For example, let’s say that you want to test the browser for availability of CSS 3D transforms. When you open the website and Modernizr runs, it will append either csstransforms3d or no-csstransforms3d class to the html element.

These classes are the easiest way to using feature detection to bulletproof your projects. You don’t have to write a single line of JavaScript. There is a significant number of web designers who know HTML and CSS and just enough of JavaScript or jQuery. Also, there are some micro projects that don’t require any JavaScript at all. So, why should you write create new JavaScript file only to put feature detection code into it? You don’t have to. Instead, use these CSS classes.

Navigation build with Modernizr

Let me give you a number of examples. I hope these examples will show you how to use feature detection with pure CSS (Sass). In order to make this easier the first example will be about creating simple navigation. Imagine you have a landing page. This landing page contains fixed navigation with six items. Let’s say that you want to use flexbox to handle the navigation. Take a look at CanIUse and you will see that flexbox is supported in current versions of major browsers.

The only exceptions are IE 8 and IE 9. In these two browsers flexbox will not work at all. IE 10 supports only flexbox 2012 syntax along with vendor prefixes (-ms-). IE 11 will be okay. Let’s also say that you want some cool hover for links. For this, we are going to use CSS 2D and 3D transforms. This means that our custom build for feature detection must contain a few detects. These include flexbox, flexbox (legacy) flexbox (tweener), CSS Transforms and CSS Transforms 3D.

Classes created by Modernizr:

<html class=" js flexbox flexboxlegacy csstransforms csstransforms3d csstransitions">

HTML Code:

<header>
 <nav>
  <ul class="nav-list">
   <li><a href="#"><span data-hover="link 1">link 1</span></a></li>
   <li><a href="#"><span data-hover="link 2">link 2</span></a></li>
   <li><a href="#"><span data-hover="link 3">link 3</span></a></li>
   <li><a href="#"><span data-hover="link 4">link 4</span></a></li>
   <li><a href="#"><span data-hover="link 5">link 5</span></a></li>
   <li><a href="#"><span data-hover="link 6">link 6</span></a></li>
  </ul>
 </nav>
</header>

Sass code:

/** 
 * Styles for browsers supporting flexbox and transforms
 */
.flexbox .nav-list {
 display: flex;
 flex-direction: column;
 justify-content: space-between;
 
 @media screen and (min-width: 480px) {
  flex-direction: row;
 }
}

.flexbox nav a {
 display: block;
 overflow: hidden;
 width: 70px;
 font-family: sans-serif;
 letter-spacing: 1px;
 text-transform: uppercase;
 color: #111;

 &:focus,
 &:hover {
  color: #666;
 }
}

.csstransforms3d nav a span {
 position: relative;
 display: inline-block;
 transition: transform 0.3s;

 &:before {
  position: absolute;
  top: 100%;
  content: attr(data-hover);
  width: 60px;
  font-weight: 700;
  transform: translate3d(0,0,0);
 }
}

.csstransforms3d nav a:hover span,
.csstransforms3d nav a:focus span {
 transform: translateY(-100%);
}

/**
 * Styles for browsers not supporting flexbox and transforms
 */
.no-flexbox li {
 @media screen and (min-width: 554px) {
  display: inline-block;
  margin: 0;
  width: 16%;
 }
}

.no-flexbox nav a {
 display: block;
 overflow: hidden;
 font-family: sans-serif;
 letter-spacing: 1px;
 text-transform: uppercase;
 color: #111;

 &:focus,
 &:hover {
  color: #666;
 }
}

Modernizr the JavaScript way

As I mentioned, you can use feature detection with Modernizr in two ways. Previously, we discussed the easy way. Now, it is time to take a look at the harder way. I should warn you that this will require writing some JavaScript. We will use the same build as we did in the previous example. The difference will be that we will now work in JavaScript. Let’s start with something easy. Say you want to test the availability of the features we used in previous example.

JavaScript code:

'use strict';

// Test for flexbox
if (Modernizr.flexbox) {
 console.log('flexbox is available.');
 /* Script A */
} else {
 console.log('flexbox is not available.');
 /* Script B */
}

// Test for CSS 3D transforms
if (Modernizr.csstransforms3d) {
 console.log('csstransforms3d are available.');
 /* Script A */
} else {
 console.log('csstransforms3d are not available.');
 /* Script B */
}

// Test for CSS 2D transforms
if (Modernizr.csstransitions) {
 console.log('csstransitions are available.');
 /* Script A */
} else {
 console.log('csstransitions are not available.');
 /* Script B */
}

We can take this feature detection even further. Modernizr API contains a number of quite powerful methods that are ready to use. For example, you can write new tests by using addTest(). Or, you can test the browsers for events by using hasEvent(). There are many more options that Modernizr API offers. There is only one thing you have to keep in mind. When you want to use certain feature detection method from the API, you have to include it in your build. On the left side, find the method under options and select it.

JavaScript code:

// Adding test for detecting jQuery
Modernizr.addTest('cryptography', function() {
 // code ...
});

// Add test for touch event
Modernizr.hasEvent('touchstart');

Beyond Modernizr and the future of feature detection

Using feature detection JavaScript library such as Modernizr will do the job. However, there is something that can reduce the need for it. Today, we can start using new CSS rule @supports for feature detection. This rule is part of the CSS3 Conditional Rules Module Level 3. Thanks to this rule, you can selectively apply CSS styles only when browser supports them. Otherwise, the browser will ignore these styles. It’s syntax is very similar to CSS media queries.

CSS code:

@supports (property: value) {
 element { … }
}

As you can see, the use of this CSS rule is similar to media queries. When you want to apply specific style to some element, you write it inside the at-rule block. @supports rule is very easy to use for CSS-based feature detection. Let’s now take this theory and try it in practice. Do you remember that navigation example? Let’s rewrite it using @supports rule instead of classes created by Modernizr library.

CSS code:

.nav-list li {
 @media screen and (min-width: 554px) {
  display: inline-block;
  margin: 0;
  width: 16%;
 }
}

/* Test for flexbox support */
@supports (display: flex) {
 .nav-list {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
 }

 @media screen and (min-width: 480px) {
  .nav-list {
   flex-direction: row;
  }
 }
 @media screen and (min-width: 554px) {
  .nav-list li {
   display: list-item;
   margin: 0;
   width: auto;
  }
 }
}

nav a {
 display: block;
 overflow: hidden;
 width: 70px;
 font-family: sans-serif;
 letter-spacing: 1px;
 text-transform: uppercase;
 color: #111;
}

nav a:focus,
nav a:hover {
 color: #666;
}

nav a span {
 position: relative;
 display: inline-block;
 transition: transform 0.3s;
}

/* Test for 3D transforms support */
@supports (transform: translate3d(0,0,0)) {
 nav a span:before {
  position: absolute;
  top: 100%;
  content: attr(data-hover);
  width: 60px;
  font-weight: 700;
  transform: translate3d(0,0,0);
 }
}

nav a:hover span,
nav a:focus span {
 transform: translateY(-100%);
}

One important thing we have to consider is current support of this CSS feature. In the beginning of this part, I said that we can start using this feature for feature detection today. Well, this depends on what browsers do you need to support. As usually, IE is the biggest troublemaker. There is not a single version of IE, from 8 to 11, that supports @supports rule. Support in the rest of the browsers is very good. Aside from support of @supports rule there is one last last thing we should discuss.

When to use CSS-based feature detection

This last thing is when should we use @supports rule and when not. The reason for this answer is that the usual answer “always” doesn’t work here. What do I mean? Take a look at the navigation example. We used CSS transition property. Did you noticed that I didn’t use @supports for it? The reason is that CSS3 transitions are currently supported in all major browsers. Transitions will work even on IE 10 and higher. You can use @supports to detect transitions.

The only problem with using @supports to detect properties such as transitions is that it’s not supported in IE 10 or 11. Imagine you are working with IE 11 and you will use @suppors rule to detect transitions. It will not work. It will not work because IE 11 doesn’t support @supports, not because it doesn’t support transitions. I was in doubt even about flexbox because it is partially supported by IE 10 and 11. Yet, I decided to use it as a simple example.

What I want to say is that you should not use @supports rule for feature detection every time. Many CSS3 feature have much better support than this rule. Instead, always double-check support for the feature you want to use on CanIUse. If the features of your choice has worse support, then use @supports. Otherwise, feature detection with Modernizr will be a better choice. Also, if you need or want to support IE 11 or older, @supports will also not helpful.

Closing thoughts on feature detection

Congrats! Now, you know a lot about feature detection. You know what’s next. You have to take everything you’ve learned and put it into practice as soon as possible. Only with deliberate practice you can make what you’ve learned stick. There is one last thing I want to say about today’s topic. It will take some time before we will be able to replace feature detection libraries with CSS @supports rule. Still, I’m very optimistic about this CSS feature. I can’t wait to see IE leaving the world for good. When this happens, web development will be much easier. What do you think?

If you liked this article, please subscribe so you don't miss any future post.

If you'd like to support me and this blog, you can become a patron, or you can buy me a coffee 🙂

By Alex Devero

I'm Founder/CEO of DEVERO Corporation. Entrepreneur, designer, developer. My mission and MTP is to accelerate the development of humankind through technology.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.