I’m currently involved in a project to write a fairly extensive set of best practices for front-end development. Alongside myself, this project includes input from a fair cross-section of my peers in the front-end development community. These best practices will be implemented alongside a coding standard as standards for development within the organisation I work for, and hopefully many other organisations when they are published.
Of all the standards that a front-end team might want to implement, those that concern the identification and graceful degradation of cross-browser feature sets can be the hardest to define.
Unfortunately, despite my respect for the developers involved, I just can’t advocate Modernizr as a solution. Let me explain why; but first, let’s revisit some concepts that are going to be quite relevant…
The web standards trifle
The separation of structure, presentation, and dynamic behaviour is imperative to web standards. To that end, Andy “Malarkey” Clarke wrote a fantastic post detailing a method he liked to use to explain this fact to clients. He entitled it “the web standards trifle”.
In Andy’s metaphor, the separation of concerns can be imagined as follows:
This layer is your content, marked-up with well structured and valid semantic HTML. This HTML provides more information about each piece of content and allows any device reading the code to output as appropriate.
This is your presentational layer. This separates all your presentational code into CSS, potentially allowing a user to override it with their own, or for you to serve something different based on context, e.g. a print or mobile stylesheet.
This is the layer that accommodates any on-page behaviours. In the world of web standards we use this layer to progressively enhance our HTML and CSS layers with more interactive magic. I’ll come back to this later.
The true virtue of this divide is that the presentational (jelly) and behavioural (custard) layers can potentially be removed or overridden without affecting each other, or the content and markup beneath. This is hugely important if we want our websites to maintain client agnosticism and improve accessibility.
Following the publication of this article there have been several suggestions that I explain the difference between graceful degradation and progressive enhancement. Suffice to say, this is something I have covered previously in another article.
Browser vs. Feature Sniffing
The old school way:
Not a good solution
There are problems with the method used above; for one thing, it is not the most direct method of identifying a browser. However, putting that aside for the moment, there are issues even with the theory of browser sniffing:
It’s quite obvious that browser sniffing does not scale. As new browsers are released, the sniffing code will need updating.
To make matters worse, many manufacturers developed their browsers to identify themselves incorrectly to get around legacy browser sniffing code. This ultimately meant that sometimes browsers were misidentified.
Ultimately browser sniffing is not a good solution. If you adopt it, you will usually end up maintaining separate streams of development whenever you need to add or update features. There are ways to mitigate that pain, but what if there was a better solution?
The obvious advantage here is that it is browser agnostic; there are no assumptions as to which browser is involved at all. Using this technique, we overcome the scalability and maintenance headaches incurred through browser sniffing, and we can develop for graceful degradation from the outset.
CSS is a DSL that lacks features
CSS is a very simple domain specific language (DSL) that functions as a series of rules that override each other in a variety of ways (the cascade). There is no mechanism to detect a specific feature or a user-agent; only media types and potentially the odd CSS hack where a user-agent has imperfectly implemented an interpreter. There are also no conditionals; you just have to use the cascade to provide alternatives based on specificity or even just source order.
With these limitations, it is impossible to implement either browser or feature sniffing in CSS. This means we need to find an alternative method to affect the cascade based on the user’s feature set.
The Modernizr method
Modernizr aims to bring an end to the UA sniffing practice. Using feature detection is a much more reliable mechanic to establish what you can and cannot do in the current browser, and Modernizr makes it convenient for you in a variety of ways:
It tests for over 20 next-generation features, all in a matter of milliseconds.
It adds classes to the
htmlelement that explain precisely what features are and are not natively supported.
Following along so far? For the most part, that all seems fairly good. What’s more, Modernizr is definitely being pushed as the solution of choice amongst the front-end community:
- Mark Pilgrim’s Dive into HTML5 “Detection” chapter
- Faruk’s “Taking Advantage of HTML5 and CSS3 with Modernizr” on A List Apart
- Web Designer Notebook: How to use Modernizr
- Image Gallery tutorial on Sitepoint
So with all that in mind, let’s go back to why I will not be recommending Modernizr as a best practice:
It’s all down to the final step outlined in the documentation I quoted earlier:
It adds classes to the
htmlelement that explain precisely what features are and are not natively supported.
html element; one for each of the features detected in the current browser. If we examine the generated source we see the following:
When I first saw that, I was horrified. Not only is it ridiculously cluttered, it also feels decidedly unsemantic and looks like a terminal case of classitis. However, let’s try to be pragmatic here; it’s a means to an end, potentially a minor side-effect to a process that will win us some power in the bigger picture. In fact, it’s one step on from Paul Irish’s proposed work around to the FOUC (flash of unstyled content) which I’ve advocated as a reasonable fallback to marking progressively enhanced objects with an “enhanced” class.
Making use of the classes
The Modernizr method allows you to assign styles based on these new classes on the document’s root element like so (example lifted from the Modernizr docs):
Here we can see that
button.glossy is first defined with a standard
png as its background. The Modernizr-added
.cssgradients class is then used to increase specificity and assign a WebKit gradient instead.
What happens if we approach the problem without the .cssgradients class:
CSS allows us to define a property twice in the same rule. It also contains built-in error handling that will ignore a property it doesn’t understand; so browsers that do not understand
-webkit-gradient(), for example, will simply ignore a property that uses it. This allows us to progressively enhance our CSS like so:
However, because we can no longer target the
.cssgradients class specifically, we cannot target the
button:hover styles only when CSS gradients are detected. Clearly this is an issue we’ll have to find some way around and is exactly what Modernizr is attempting to solve.
This all seems as if Modernizr is giving us a great deal of power, however, it comes with a significant cost.
It would only be the feature-sniff-dependant CSS rules that would fail, since those classes would now be missing. Potentially this only means the loss of a few nice in-browser rendering optimisations. However, it also means there is a significant loss in the overall structure of specificity within your stylesheets, which could easily cascade unintended effects through to other elements. This could easily result in some content being styled to be unreadable, or worse, incorrectly hidden. This would be very bad indeed.
With that in mind, what other options are available that allow us to target our CSS?
Server-side browser sniffing
We could browser sniff on the server and add a class to the
html element as we generate the HTML that is delivered in the HTTP response. This could result in something like the following:
This means we can target CSS with the
.firefox class in much the same way that we used the feature classes that Modernizr provided. There is no way for us to perform feature sniffing on the server; the best we could offer is a feature lookup based on the user-agent string that we’ve used to sniff the browser, but that would ultimately still be browser sniffing.
In fact, the best browser sniffing solution would allow us a little more flexibility on browser versions:
Note I’ve added several classes this time; one for the rendering engine, one for the browser, and one that contains the major and minor versions. This would allow us more specificity.
However, server-side browser sniffing means that you’ll run into issues if your pages are publicly cacheable. This server-side solution results in a different HTTP response (the HTML) for each differing browser.
If you utilise any intermediary caching (Squid, Varnish, or a custom origin CDN) for static pages, and you use server-side browser detection, you need to make sure those caches don’t inadvertently send the wrong content to the wrong browser. To do this you’ll need to
Vary: User-Agent header in the HTTP response. This instructs any intermediary caches to store multiple copies of the page (one for each
User-Agent string that it sees) and to inspect the incoming
User-Agent string when looking for cached responses to the current request.
IE targeting with conditional comments
If we’re entirely honest with ourselves as web developers, we can probably admit that the majority of woes we experience in CSS are as a direct result of features that any given version of Internet Explorer has not implemented. In fact, I regularly interview developers who advocate maintaining a separate stylesheet for IE that is included through the use of IE’s conditional comments. Having attempted to use this method in the past, I can confidently say that it is a maintenance nightmare and quickly becomes pretty unmanageable.
A better technique would be to adopt conditional comments to add IE specific classes in much the same way we have with Modernizr or server-side browser sniffing. This method was first proposed by none other than Paul Irish back in 2008 in his article "Conditional stylesheets vs. CSS hacks? Answer: neither". With that method, our
html element would look like this:
We can even make it more specific if we so desire:
The only flaw with this solution is that we can only target versions of Internet Explorer. No other browser implements conditional comments.
Use CSS properly and make use of the cascade
As previously stated, CSS is designed to cascade. This means the built-in error handling will ignore properties the interpreter does not understand, and we can override rules based on specificity and source order. In most cases, these key features are more than adequate to develop gracefully degrading stylesheets.
Most capable front end developers are already doing this and coping just fine. Add this to the previous IE targeting approach and you’ll find that you do not need to over-engineer a solution.
So, in summary then, I absolutely cannot recommend implementing Modernizr as a best practice for front end development. It attempts to solve a problem from the wrong direction, and introduces a new potential point of failure. What’s more it breaks the fundamental ethic of web standards; the separation of concerns.
I continue to recommend well crafted, gracefully degrading CSS, backed up by conditional commented classes on the
html element for targeting IE.