Conditional Love

“Browser.” The four-letter word of web design.

I mean, let’s face it: on the good days, when things just work in your target browsers, it’s marvelous. The air smells sweeter, birds’ songs sound more melodious, and both your design and your code are looking sharp.

But on the less-than-good days (which is, frankly, most of them), you’re compelled to tie up all your browsers in a sack, heave them into the nearest river, and start designing all-imagemap websites. We all play favorites, after all: some will swear by Firefox, Opera fans are allegedly legion, and others still will frown upon anything less than the latest WebKit nightly.

Thankfully, we do have an out for those little inconsistencies that crop up when dealing with cross-browser testing: CSS patches.

Spare the Rod, Hack the Browser

Before committing browsercide over some rendering bug, a designer will typically reach for a snippet of CSS fix the faulty browser. Historically referred to as “hacks,” I prefer Dan Cederholm’s more client-friendly alternative, “patches”.

But whatever you call them, CSS patches all work along the same principle: supply the proper property value to the good browsers, while giving higher maintenance other browsers an incorrect value that their frustrating idiosyncratic rendering engine can understand.

Traditionally, this has been done either by exploiting incomplete CSS support:

#content {
	height: 1%;	 // Let's force hasLayout for old versions of IE.
	line-height: 1.6;
	padding: 1em;
}
html>body #content {
	height: auto; // Modern browsers get a proper height value.
}

or by exploiting bugs in their rendering engine to deliver alternate style rules:

#content p {
	font-size: .8em;
	/* Hide from Mac IE5 \*/
	font-size: .9em;
	/* End hiding from Mac IE5 */
}

We’ve even used these exploits to serve up whole stylesheets altogether:

@import url("core.css");
@media tty {
	i{content:"\";/*" "*/}} @import 'windows-ie5.css'; /*";}
}/* */

The list goes on, and on, and on. For every browser, for every bug, there’s a patch available to fix some rendering bug.

But after some time working with standards-based layouts, I’ve found that CSS patches, as we’ve traditionally used them, become increasingly difficult to maintain. As stylesheets are modified over the course of a site’s lifetime, inline fixes we’ve written may become obsolete, making them difficult to find, update, or prune out of our CSS. A good patch requires a constant gardener to ensure that it adds more than just bloat to a stylesheet, and inline patches can be very hard to weed out of a decently sized CSS file.

Giving the Kids Separate Rooms

Since I joined Airbag Industries earlier this year, every project we’ve worked on has this in the head of its templates:

<link rel="stylesheet" href="-/css/screen/main.css" type="text/css" media="screen, projection" />
<!--[if lt IE 7]>
<link rel="stylesheet" href="-/css/screen/patches/win-ie-old.css" type="text/css" media="screen, projection" />
<![endif]-->
<!--[if gte IE 7]>
<link rel="stylesheet" href="-/css/screen/patches/win-ie7-up.css" type="text/css" media="screen, projection" />
<![endif]-->

The first element is, simply enough, a link element that points to the project’s main CSS file. No patches, no hacks: just pure, modern browser-friendly style rules. Which, nine times out of ten, will net you a design that looks like spilled eggnog in various versions of Internet Explorer.

But don’t reach for the mulled wine quite yet. Immediately after, we’ve got a brace of conditional comments wrapped around two other link elements. These odd-looking comments allow us to selectively serve up additional stylesheets just to the version of IE that needs them. We’ve got one for IE 6 and below:

<!--[if lt IE 7]>
<link rel="stylesheet" href="-/css/screen/patches/win-ie-old.css" type="text/css" media="screen, projection" />
<![endif]-->

And another for IE7 and above:

<!--[if gte IE 7]>
<link rel="stylesheet" href="-/css/screen/patches/win-ie7-up.css" type="text/css" media="screen, projection" />
<![endif]-->

Microsoft’s conditional comments aren’t exactly new, but they can be a valuable alternative to cooking CSS patches directly into a master stylesheet. And though they’re not a W3C-approved markup structure, I think they’re just brilliant because they innovate within the spec: non-IE devices will assume that the comments are just that, and ignore the markup altogether.

This does, of course, mean that there’s a little extra markup in the head of our documents. But this approach can seriously cut down on the unnecessary patches served up to the browsers that don’t need them. Namely, we no longer have to write rules like this in our main stylesheet:

#content {
	height: 1%;	// Let's force hasLayout for old versions of IE.
	line-height: 1.6;
	padding: 1em;
}
html>body #content {
	height: auto;	// Modern browsers get a proper height value.
}

Rather, we can simply write an un-patched rule in our core stylesheet:

#content {
	line-height: 1.6;
	padding: 1em;
}

And now, our patch for older versions of IE goes in—you guessed it—the stylesheet for older versions of IE:

#content {
	height: 1%;
}

The hasLayout patch is applied, our design’s repaired, and—most importantly—the patch is only seen by the browser that needs it. The “good” browsers don’t have to incur any added stylesheet weight from our IE patches, and Internet Explorer gets the conditional love it deserves.

Most importantly, this “compartmentalized” approach to CSS patching makes it much easier for me to patch and maintain the fixes applied to a particular browser. If I need to track down a bug for IE7, I don’t need to scroll through dozens or hundreds of rules in my core stylesheet: instead, I just open the considerably slimmer IE7-specific patch file, make my edits, and move right along.

Even Good Children Misbehave

While IE may occupy the bulk of our debugging time, there’s no denying that other popular, modern browsers will occasionally disagree on how certain bits of CSS should be rendered. But without something as, well, pimp as conditional comments at our disposal, how do we bring the so-called “good browsers” back in line with our design?

Assuming you’re loving the “one patch file per browser” model as much as I do, there’s just one alternative: JavaScript.

function isSaf() {
	var isSaf = (document.childNodes && !document.all && !navigator.taintEnabled && !navigator.accentColorName) ? true : false;
	return isSaf;
}
function isOp() {
	var isOp = (window.opera) ? true : false;
	return isOp;
}

Instead of relying on dotcom-era tactics of parsing the browser’s user-agent string, we’re testing here for support for various DOM objects, whose presence or absence we can use to reasonably infer the browser we’re looking at. So running the isOp() function, for example, will test for Opera’s proprietary window.opera object, and thereby accurately tell you if your user’s running Norway’s finest browser.

With scripts such as isOp() and isSaf() in place, you can then reasonably test which browser’s viewing your content, and insert additional link elements as needed.

function loadPatches(dir) {
	if (document.getElementsByTagName() && document.createElement()) {
		var head = document.getElementsByTagName("head")[0];
		if (head) {
			var css = new Array();
			if (isSaf()) {
				css.push("saf.css");
			} else if (isOp()) {
				css.push("opera.css");
			}
			if (css.length) {
				var link = document.createElement("link");
				link.setAttribute("rel", "stylesheet");
				link.setAttribute("type", "text/css");
				link.setAttribute("media", "screen, projection");
				for (var i = 0; i < css.length; i++) {
					var tag = link.cloneNode(true);
					tag.setAttribute("href", dir + css[0]);
					head.appendChild(tag);
				}
			}
		}
	}
}

Here, we’re testing the results of isSaf() and isOp(), one after the other. For each function that returns true, then the name of a new stylesheet is added to the oh-so-cleverly named css array. Then, for each entry in css, we create a new link element, point it at our patch file, and insert it into the head of our template.

Fire it up using your favorite onload or DOMContentLoaded function, and you’re good to go.

Scripteat Emptor

At this point, some of the audience’s more conscientious ‘scripters may be preparing to lob figgy pudding at this author’s head. And that’s perfectly understandable; relying on JavaScript to patch CSS chafes a bit against the normally clean separation we have between our pages’ content, presentation, and behavior layers.

And beyond the philosophical concerns, this approach comes with a few technical caveats attached:

Browser detection? So un-133t.

Browser detection is not something I’d typically recommend. Whenever possible, a proper DOM script should check for the support of a given object or method, rather than the device with which your users view your content.

It’s JavaScript, so don’t count on it being available.

According to one site, roughly four percent of Internet users don’t have JavaScript enabled. Your site’s stats might be higher or lower than this number, but still: don’t expect that every member of your audience will see these additional stylesheets, and ensure that your content’s still accessible with JS turned off.

Be a constant gardener.

The sample isSaf() and isOp() functions I’ve written will tell you if the user’s browser is Safari or Opera. As a result, stylesheets written to patch issues in an old browser may break when later releases repair the relevant CSS bugs.

You can, of course, add logic to these simple little scripts to serve up version-specific stylesheets, but that way madness may lie. In any event, test your work vigorously, and keep testing it when new versions of the targeted browsers come out. Make sure that a patch written today doesn’t become a bug tomorrow.

Patching Firefox, Opera, and Safari isn’t something I’ve had to do frequently: still, there have been occasions where the above script’s come in handy. Between conditional comments, careful CSS auditing, and some judicious JavaScript, browser-based bugs can be handled with near-surgical precision.

So pass the ‘nog. It’s patchin’ time.

About the author

Ethan Marcotte is a web designer and developer who cares about beautiful design, elegant code, and how the two intersect. He is currently working on a book about responsive web design, and drinking entirely too much coffee.

He swears profusely on Twitter, and would like to be an unstoppable robot ninja when he grows up. Beep.

Photo: Brian Warren

More articles by Ethan

Comments