Bulletproof Cross-Browser CSS Techniques

Klemen Slavič | October 6th, 2010

As we're all pretty well aware, the browser landscape is a diverse population of software implementations of various HTML, CSS and JavaScript specifications. While some implement the standards to the letter, others are much lax in their interpretation of the rules, which leads to inconsistencies when rendering pages to the screen.

To understand how this situation came about and help us understand the ways these facts can help us better navigate the current browser landscape and prepare our documents for future browser compatibility.

This article assumes support for Grade A browsers, according to Yahoo's chart.

A Trip Down Memory Lane

Some of you might be old enough to remember the browsers of the olden days. The very first graphical browser, written by Tim Berners Lee in 1990, sparked the revolution that led to the adoption of the SGML flavor we have come to call HTML today.

The format was a good trade-off between human readability and simple machine processing. Coupled with very simple rendering rules, building a web browser was a very simple matter. It wasn't long until the Mosaic browser was created, which was soon followed by a series of events we have lovingly come to call The Great Browser Wars.

The Plot Thickens

Not unlike the Cold War, there were two sides to this conflict: Netscape and Microsoft. While Netscape soon realized the future of the web, forking the Mosaic browser in late 1994, it took Microsoft a year to decide on its stance towards the then-emerging Internet. They, too, based their browser on the Mosaic code base.

With both vendors poised to be crowned King of the Internet (ah, aren't we all?), both went into feature overdrive in order to blow the other's browser out of the water. With such a frantic and chaotic development cycle, one thing became obscured at the whims of the battling parties – cross-browser compatibility became an aside, something to consider when bigger customers wanted to defect to the other's browsing platform.

Guess That Tag!

Some soon recognized the need to standardize the web to avoid fragmentation, an effort that resulted in the formation of the World Wide Web consortium, the W3C. But unlike a democratic state with a functioning police force to enforce the rules and standards proposed by the consortium, no one could really force vendors to abide by the specifications. The web (and its future) was at the mercy of the two companies, regardless of what the W3C had to say about it.

The attrition eventually led to Netscape's downfall; unable to sustain further development and hold onto market share in light of Microsoft's decision to bundle Internet Explorer as part of Windows (which led to an antitrust case being brought against Microsoft because of it), Netscape folded and released the source code to the Netscape browser, codenamed Mozilla, in 1998. This was the death knell that marked the end of the company, which was bought by America OnLine in late 1998.

The Day the Web Stopped

With no competition in sight, Microsoft slowed the development of Internet Explorer, eventually settling on version 6.0, which until recently was the dominant browser on the web, with a market share exceeding 90%.

... until Firefox (based on Mozilla) showed up, followed by a multitude of WebKit-based implementations.

The Aftermath

So why the long-winded history lesson? There's a key lesson to be learned here: specifications are only as good as their implementations across browsers.

Take the Box Model, for example. It's the basis of all presentational elements used in HTML and CSS, yet its implementation is differently implemented in Internet Explorer and other browsers. In IE6 Quirks Mode (and older IE versions), the width and heightof an element include the border and padding, while standards dictate that the two dimensions should contain only the dimensions of the content. This was a fundamental difference between browsers that made cross-browser web site development a nightmare filled with various hacks, such as the one developed by Tantek Çelik et al.

If you're anything like me, the notion that you'd have to write CSS like that makes you shudder. But don't fret – by knowing which browsers you're targeting you can apply what I like to call progressive degradation.

What We've Learned

To avoid mistakes of the past and prepare for the future, we need to tell browsers that the documents we write are documents meant to be rendered in standards mode using the right DOCTYPE. This levels the playing field for all of today's browsers to a common rendering mode, eliminating the need for the Box Model Hack.

With significant research and testing invested in the development of HTML5 by the WHATWG, it's been shown that there exists a minimal DOCTYPE declaration that causes all browsers to switch to standards mode:

<!DOCTYPE html>

Coincidentally, this is the DOCTYPE of choice for use in HTML5 documents and prescribed by the HTML5 specification. Considering the drive towards implementing the standard by all the relevant browser vendors in near-future releases, this is the DOCTYPE to use for any web page you're developing today.

Given that we're already using the correct rendering mode, there are still subtle (and some not so subtle) differences between browsers and different versions. Considering that we've had ample time to stumble upon and study the different bugs, efforts to document these have resulted in the Position Is Everything website where anyone can look up the bugs and fixes for the problems.

Also, when dealing with Internet Explorer, there is a special meta tag introduced in version 8.0 that lets you use the rendering engine of the previous version, called X-UA-Compatible. It's inserted into the head of the document and specifies the rendering mode for IE:

<head> 
<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7" /> 
... 
</head>

Substituting EmulateIE7 for edgewill force IE to use the latest rendering engine, whether it be 8.0 or later.

Personally, I've steered clear of the whole X-UA-Compatible meta directive, since in practice, IE7 emulation does not identically match the native rendering and makes your pages future-incompatible, since it locks the rendering of your page to a specific version of the IE engine. Do not use it.

Also, if at all possible, stick to using CSS 2.1 selectors for now. Until there's mainstream adoption of IE9, there's no point in using them, since you'll have to rewrite all CSS3 selectors for IE8 and below anyways.

Divide and Conquer

To ensure that our documents are valid and correctly display in the latest and greatest browsers we design our pages in such browsers. Personally, I stick to Firefox and Chrome for the initial design due to their superior debugging tools. This also ensures that the site I'm building is as future-ready as possible before back-porting and overriding CSS styles for older and buggy browsers.

At this point you may be wondering whether to check older versions of Firefox, Chrome and/or Opera for correct rendering. This depends on your target audience and the market share of browsers you need to support, but major version upgrades are much more common among Firefox, Chrome, Safari and Opera users than among IE. In my experience, testing the latest stable versions and Firefox 2 is sufficient and covers more than 95% of the desktop market (if you include IE6-8 on the list).

Exploring the Wilderness

There are some bugs involving older versions of browsers, but these bugs were fixed long ago and their relative market share is well within the ignorable margin. The only other two browsers still having a small but (arguably) significant share are Safari and Firefox 2.

First, let’s consider the Mac platform – OSX comes bundled with Safari, a WebKit-based browser. It used to be available solely for the Mac, but a Windows version has been around since 3.0.1. Since versions prior to the release of 3.0.1 only run on Mac hardware and OS, there’s no way to test rendering on older versions of the browsers without buying a second-hand or downgraded Mac computer. Luckily, as these statistics show, this point is made moot, since Mac users diligently keep up with software updates, with a negligible fraction of users on version 3, which you can test on Windows.

From my experience, the rendering engine is identical between platforms (apart from the font antialiasing and some gamma curve mismatches in very early versions), so we can safely assume that testing the browser on Windows from version 4 onwards will give more than adequate results.

There is, however, one bug in Firefox 2 that you should be aware of – image scaling. Whenever an image scales up or down, significant aliasing occurs, which renders images using a nearest-neighbour algorithm instead of bilinear filtering (see images for comparison).


1. Original image


2. Scaled down image in Firefox 2


3. Scaled down image in Firefox 3 and above

There is no cure for this disease, sadly, but rather something to look out for when designing for this specific platform.

As an aside – IE7 and below suffer the same aliasing effect that Firefox 2 exhibits, but we can mitigate this using IE proprietary CSS filters. To fix this issue, you can use Javascript to replace images with same-sized transparent GIFs, set the source of the image as the background for that element and use the AlphaImageLoader proprietary filter to apply the correct rendering. The implementation of this is long and hard without intricate knowledge of DOM manipulation, so I’d recommend using a prepared script in this case, conveniently located here. All that’s then needed is a little additional Javascript to enable the filters:

window.onload = function() { imgSizer.collate(); };

For more information on that script and the technique, see the Fluid Images article from Ethan Marcotte (the author of said script).

Also, as introduced in IE7, you can specify the resize algorithm using a proprietary CSS property called -ms-interpolation-mode:

img { -ms-interpolation-mode: bicubic }

This will switch from the default nearest-neighbour resizing to a bicubic filtering algorithm, thus making images appear smooth when resized. Having said that, while it appears that this resolves jaggy image issues, it does slow down rendering when dynamically resizing images. Use with care.

No special attention is needed for IE8; the browser does, by default, use antialiasing for resizing images.

IE8 Compatibility

After I've got my site looking the way I like, I start out by opening the page in Internet Explorer 8 and verifying the page renders correctly. Since IE8 has a pretty solid implementation of CSS2.1 I rarely have to change my style sheets to match the rendering of other browsers. There are a few points to be aware of, and a chart of CSS compatibility compiled by Peter Paul Koch documents the missing or incomplete implementations including tests you can run on your own copy of the browser.

A few things that are available in all modern browsers but are missing from IE8 are:

  • opacity support; this can be achieved using proprietary filters, which will be discussed later,
  • rgba() color support,
  • text-shadow,
  • box-shadow (implemented on Gecko and Webkit browsers using -moz-box-shadow and -webkit-box-shadow, respectively),
  • border-radius (implemented on Gecko and Webkit browsers using -moz-border-radius and -webkit-border-radius, respectively, and on Opera natively).

These CSS declarations are part of the CSS3 spec and will be supported in IE9, but are currently being used in other browsers with graceful degradation for IE. If that is unacceptable for your use case, you have two options – use proprietary IE filters or implement image, which may increase markup complexity and may require JavaScript.

In case I need to tweak or fix any errors in the layout, I use conditional comments to include an IE-specific stylesheet:

<!--[if lte IE 8]> 
<link rel="stylesheet" href="ie.css" /> 
<![endif]-->

This way, I can target fixes to IE8 and below without worrying about breaking future IE versions and other browsers.

IE7 – the Fun Begins

Next, I fire up a copy of Internet Explorer 7.0 in a virtual machine (you can download the various OS and browser version combinations from Microsoft's download site here). At this point, there are some things that might fail:

  • some CSS2.1 selectors fail to operate properly, namely + (next sibling), :active, :first-child (when changing the DOM dynamically),
  • display: inline-block, content, list-style and outline fail to work,
  • additional bugs related to the Guillotine bug, dynamic position switching and using floats incorrectly positions, clips or hides content (see Peekaboo bug),
  • floated elements clipped or collapsed, solved by applying the hasLayout fix.

For these, I add another conditional comment that targets IE7 and below:

<!--[if lte IE 7]> 
<link rel="stylesheet" href="ie7.css" /> 
<![endif]-->

IE6 Compatibility (or: "We don't serve your kind here!")

After I fix up IE7 I evaluate whether I need support for the dreaded Internet Explorer 6.0. Even though Microsoft is trying really hard to migrate users away from 6 to later versions (they even sent flowers to the funeral!), the adoption of later versions has been slow and painful. Even the UK government acknowledged the fact that they should move to upgrade the browser, but they stated that the costs involved are just too high to warrant an upgrade.

If you're planning on publishing a general public site, you may not need to worry about IE6, but if clients or project specifications dictate compatibility with this browser, there are additional issues you need to address before you can declare IE6 a done deal:

  • some CSS2.1 selectors fail, namely: > (direct child), [x] attribute selector, multiple class selectors (eg. p.one.two), :hover and :active (work only on links) and :first-child,
  • no min/max-width/height, overflow: visible is buggy, position: fixed is unsupported,
  • PNG-24 alpha-transparent images are rendered with an opaque gray background; can be fixed using the proprietary AlphaImageLoader, but it significantly degrades performance; 8-bit PNG images render correctly, though.

In addition to the above limitations, there's a much more dire bug that needs special care: the double margin bug. Basically, whenever you use float: left|right in your code, make sure to add display: inline to the declaration to force it to display the correct margin length. Other browsers will safely ignore the inline declaration, since all floats are blocks implicitly. This trick can be used every time you declare a float, which will save you trouble whenever the need arises to back-port your site to IE6.

As with previous versions, I use a separate stylesheet for IE6 and below:

<!--[if lte IE 6]> 
<link rel="stylesheet" href="ie6.css" /> 
<![endif]-->

Now you may be thinking, "That’s a hell of a lot of style sheets." And you'd be correct. There is a way to target different versions of the browser using CSS hacks, discussed in the section below, but before you use them, ask yourself – will users really notice the performance degradation an additional style sheet request entails when their browser runs significantly slower as their modern counterparts anyways?

Also, this method reduces the need to prune old and obsolete rules from style sheets, since newer browsers will simply ignore the style sheets and therefore make no additional requests.

There's also an additional bug that you have probably crossed paths with – the infamous PNG transparency rendering bug. Sadly, there's no CSS-only solution to this problem, but at least it can be fixed using JavaScript.

There are numerous solutions to this problem, but one of the most versatile seems to be DD_belatedPNG. Due to the nature of the fix there are some known issues, but these can be worked around in most cases.

Using Bugs to Your Advantage

Some bugs are better than others. In this case, CSS parsing bugs can help us target specific versions of IE using a specially crafted selector:

  • for IE7, prepend any rule with *+html,
  • for IE6, prepend any rule with * html.

An example of a stylesheet containing rules for IE7 and IE6 compatibility:

div.highlight { 
background: red;    
float: left; 
margin-right: 10px; 
outline: 1px solid blue; 
} 

/* IE7 doesn't support outline, use border instead */ 
*+html div.highlight { 
border: 1px solid blue; 
margin: -1px; 
margin-right: 9px; 
} 

/* IE6 needs to fix doubled margin bug */ 
* html div.highlight { 
display: inline; 
}

So far, there has been no CSS parsing bugs identified that would help us target IE8 using a selector only. What we can do, however, is use declaration parsing bugs to target specific versions:

  • to target IE8 in standards mode, use /*\**/ before the colon and apply the \9 suffix to the value declaration,
  • to target IE8 and below, use \9 before terminating a CSS value declaration,
  • to target IE7 and below, use the * prefix before a CSS property declaration,
  • to target IE6, use the _ (underscore) prefix before a CSS property declatarion

To illustrate, let's apply a few of these rules just for fun:

.myClass { 
color: black; /* normal CSS declaration */ 
color /*\**/: red\9; /* IE8 standards mode */ 
color: green\9; /* IE8 and below */ 
*color: blue; /* IE7 and below */ 
_color: purple; /* IE6 */ 
}

Using CSS hacks enables us to apply fixes without the need for conditional comments, but at a cost – hacks usually don't validate, are hard to understand without additional comments and can be confusing for IDEs, depending on their validation and syntax highlighting implementations.

Given the choice between CSS hacks and conditional comments, I always pick the latter as my go-to method when dealing with browser version targeting. It has served me and my clients well in the past and continue to make my code more standards-oriented, readable and future proof – who knows when or if these hacks will start interfering with newer or other vendors' browser parsers. Granted, if all you need is to fix a declaration or two, you can still use this in your existing style sheets, but as soon as that number grows beyond, say, a half a dozen, you should consider creating a separate style sheet.

Pound Some Sense Into That Browser

The endless tweaking and fiddling that web design brings with it has made people question whether we can leverage other technologies to convince browsers to behave more like their modern, standards-based brethren, without the need for conditional comments or CSS hacks. There are many different solutions, some involving swapping out the browser's proverbial brain, others involving subtler means of persuasion – JavaScript.

Dean Edwards was probably the first person to comprehensively test and implement modern features for the IE family of browsers. He called it IE7.js – the drop-in script provides a straight-forward way of implementing the features without using hacks or conditional style sheets. In addition to supporting CSS properties it fixes many other issues and bugs present in older browsers, including fixing PNG alpha transparency in IE5 and IE6.

A more recent endeavor into the race to propel the IE browser family into the 21st century is the ie-css3.js library by Keith Clark. As the name suggests, the script implements CSS3 selectors for the browsers to enable them to use advanced rules that are currently unavailable in said browsers.

Waste Disposal

As anyone with experience with any long-term or large-scale website development can relate, there's nothing more depressing than having to debug vast amounts of CSS with no clear indication of which DOM elements they apply to or why. Usually, as projects grow, so does the number of rules, since rarely does anyone take the time to audit CSS code; rather, most just opt to append new rules to style sheets.

As the number of rules grows, so does the need to manage those rules, as maintainability is directly related to the length of style sheets. Here are a few guidelines to help figure out the best way to

Reduce, recycle

The first and most important rule is to keep your rule sets as simple as possible. Use overrides and priorities to your advantage. Take styling paragraphs, for example. One might come up with a case for highlighting different sections of text using different classes:

p.leading { 
font-size: 16px; 
font-style: italic; 
color: #999; 
margin-bottom: 16px; 
} 

p.quote { 
font-size: 12px; 
color: #666; 
margin-left: 40px; 
} 

p.normal { 
font-size: 12px; 
color: #666; 
margin-bottom: 12px; 
}

In such a case, it is wise to ask yourself whether you can use a base type instead of a class to help with CSS property inheritance. Say we decide .normal is a good candidate and reduce that to a base definition, getting rid of redunant rules:

p { 
font-size: 12px; 
color #666; 
margin: 0; 
margin-bottom: 12px; 
} 

p.quote { 
margin-left: 40px; 
} 

p.leading { 
font-size: 16px; 
font-style: italic; 
color: #999; 
margin-bottom: 16px; 
}

By reducing code like that, we've enabled future changes to affect all paragraphs until overridden. And best of all, we didn't have to touch HTML at all. Semantics is key. Whenever possible, apply rules to base types and override in class-specific ones.

Ideally, you'd want to reconsider p.quote and p.leading as candidates for replacement; e.g. p.quote could become blockquote while p.leading (assuming it's the first paragraph within a container) could become p:first-child.

This approach does, however, require an eye for detail on the part of implementer, since one must identify common enough properties to be able to create a base style and variations. This is thoroughly discussed in Nicole Sullivan'sObject-Oriented CSS and Natalie Downe'sWriting Maintainable CSS.

Removing unnecessary cruft

At this point, a shameless plug is in order. I've written a jQuery based tool that analyzes the current page and reports rules that do not match any elements on the current page – the Unused Styles Plugin. It is currently in development, but works well enough to run on development project as a quick overall review tool. See the docs and online demo for more information.

Hacks vs. conditional comments

So what's the best way to address browser inconsistencies? Well, from my experience, I've decided on a strategy that seems to work best: let the oldest browser carry the burden of compatibility.

Remember, you are building websites that, ideally, should not need periodic check-ups and redevelopment (save for the obligatory redesigns, of course). So why should the browsers of tomorrow carry the burden of the browsers of yesteryear?

The way I've tackled this problem is including the following <head> structure:

<link rel="stylesheet" href="bridging.css"/> 
<!--[if lte IE 8]> 
<link rel="stylesheet" href="ie8.css"/> 
<![endif]--> 
<!--[if lte IE 7]> 
<link rel="stylesheet" href="ie7.css"/> 
<![endif]--> 
<!--[if lte IE 6]> 
<link rel="stylesheet" href="ie6.css"/> 
<![endif]-->

In this case, IE6 will load all four style sheets (including additional @imports), and each subsequent version of IE will load one less. The beauty of this is that all CSS bugs are dealt with in a backwardly fashion, meaning rules need not be overridden for posterity. And when support for a specific version is dropped, so can the conditional comment, reducing the CSS code needed to maintain the website.

All’s Well that Ends Well

We've covered most of the obvious cases where forethought can help us plan for a better development experience and maintainability, but, as the browser landscape evolves in the wake of major announcements from browser vendors, expect to expand on the given knowledge, since HTML5 and CSS3 are just around the corner.

As always, I'd like to hear your own comments and questions regarding your solutions for cross-browser development, so leave your comments or hit me up on Twitter.

Appendix

I've added a short list of tools and references that might help with your CSS endeavours:

 

About the Author

Klemen Slavič works as a frontend developer and trainer at Kompas Xnet d.o.o., working on desktop and web applications on the .NET platform, including WPF, Silverlight and ASP.NET. He currently holds MCT and MCPD Web certifications and is also an ACE in Photoshop. He's a strong proponent of semantics and progressive enhancement.

He started programming in 1996, working his way up from writing fractal renderers in Logo and BASIC into the Windows environment with VB6 and eventually moving to the web. He fell in love with HTML, CSS and Javascript, which is currently his primary career and hobby focus.

In his free time he likes to keep on top of various emerging web technologies, closed- or open-source alike, and enjoys working on media-rich projects and performances, some documented on his Slovene blog, Animalija.

Find Klemen on: