BTreeHugger's Beat

Mutually-exclusive stylesheets

If you've ever wanted to serve one CSS stylesheet to modern browsers, and a basic one to older browsers, this is possibly the trick for you. Essentially this technique means less futzing around "reversing" rules, and still gives you an easy place to add rules that might as well be common to all browsers.

Using support for Data URIs as a cut-off

There is no magic. The key insight is to use the data protocol on browsers that support it, which are browsers likely to support CSS2 very well (IE8 supports data URIs now, incidentally). Older browsers, including Mozillas, Safaris, and so forth, are basically disused. Even if they can hypothetically support CSS2 well, newer versions are available that users should be using without question, even by my standards. I aim to create a lo-fi stylesheet for mobile devices, but make it fluid enough to handle desktop browsers as well.

Using data URIs we can start with a basic link tag. We load a data URI that contains a stylesheet with a single import statement. A major caveat with this approach is that you must provide a fully-qualified URL for the stylesheet to be imported, otherwise Gecko doesn't seem to load it at all:

<link rel="stylesheet" type="text/css" href="data:text/css,@import%20'http://fqdn/modern.css';" />

Now we have to import the basic stylesheet for browsers that cannot handle the data URI protocol. Mercifully this isn't too hard, since most browsers only load the first stylesheet they come across in a link tag and ignore the rest. As a consequence, older browsers won't find the first stylesheet and will load the second. Thus we can simply toss in a second link after the first:

<link rel="stylesheet" type="text/css" href="data:text/css,@import%20'http://fqdn/modern.css';" /> <link rel="stylesheet" type="text/css" href="basic.css" />

Now we have to deal with Opera, which will load the second stylesheet instead of the first. This is thankfully trivial, we simply have to assign titles to each stylesheet, which forces Opera to load only the first one (due to the CSS spec saying it must). As a side-benefit, doing so lets us specify the second stylesheet as an alternate stylesheet, and modern browsers with a UI for changing stylesheets will list them both:

<link rel="stylesheet" title="Modern" type="text/css" href="data:text/css,@import%20'http://fqdn/modern.css';" /> <link rel="alternate stylesheet" title="Basic" type="text/css" href="basic.css" />

The only problem is that Opera 7.1x didn't support the data protocol, and will therefore get no styles whatsoever.. this is probably not something that can be easily rectified, but all other Opera versions, including 7.0x and 7.2 up will have no trouble, so it's probably safe to just ignore the problem.

Dealing with Internet Explorer

By default, with what we've got right now, Internet Explorers 8 and up will read both basic and modern stylesheets, while 7 and under will read just the basic one due to not supporting Data URIs. We have to decide what we want.. odds are that versions 7 and up are good enough unless you want to target only version 9 and up for it's advanced CSS3 support.

It's easiest to just hide both stylesheets from all Internet Explorer to begin with:

<!--[if !IE]><!--> <link rel="stylesheet" title="Modern" type="text/css" href="data:text/css,@import%20'http://fqdn/modern.css';" /> <link rel="alternate stylesheet" title="Basic" type="text/css" href="basic.css" /> <!--<![endif]-->

And now we can use more conditional comments to set our cut-off point wherever we would like:

<!--[if !IE]><!--> <link rel="stylesheet" title="Modern" type="text/css" href="data:text/css,@import%20'http://fqdn/modern.css';" /> <link rel="alternate stylesheet" title="Basic" type="text/css" href="basic.css" /> <!--<![endif]--> <!--[if gt IE 7]><link rel="stylesheet" type="text/css" href="modern.css" /><![endif]--> <!--[if lt IE 8]><link rel="stylesheet" type="text/css" href="basic.css" /><![endif]-->

And there we have it. Of course, some browsers actually parse conditional comments and pretend to be Internet Explorer (Playstation browser, etc). I haven't tried these browsers yet, but it ought to be possible to choose a value that will ensure they get the right version.

Rules common to all browsers and devices

Of course, any additional stylesheets without a title and which aren't alternate will be loaded by a browser in addition to the others:

<!--[if !IE]><!--> <link rel="stylesheet" title="Modern" type="text/css" href="data:text/css,@import%20'http://fqdn/modern.css';" /> <link rel="alternate stylesheet" title="Basic" type="text/css" href="basic.css" /> <!--<![endif]--> <!--[if gt IE 7]><link rel="stylesheet" type="text/css" href="modern.css" /><![endif]--> <!--[if lt IE 8]><link rel="stylesheet" type="text/css" href="basic.css" /><![endif]--> <link rel="stylesheet" type="text/css" href="common.css" />

Non-screen stylesheets

As always, the media type of your linked stylesheets makes for a convenient method to provide handheld and print stylesheets:

<link rel="stylesheet" type="text/css" media="print" href="print.css" /> <!--[if !IE]><!--> <link rel="stylesheet" type="text/css" media="handheld" href="handheld.css" /> <link rel="stylesheet" title="Modern" type="text/css" href="data:text/css,@import%20'http://fqdn/modern.css';" /> <link rel="alternate stylesheet" title="Basic" type="text/css" href="basic.css" /> <!--<![endif]--> <!--[if gt IE 7]><link rel="stylesheet" type="text/css" href="modern.css" /><![endif]--> <!--[if lt IE 8]><link rel="stylesheet" type="text/css" href="basic.css" /><![endif]--> <link rel="stylesheet" type="text/css" href="common.css" />

Unfortunately many mobile browsers tend to consider themselves screen devices. To prevent smaller screens from getting our modern stylesheet, we can use advanced media queries to make them mutually-exclusive:

<link rel="stylesheet" type="text/css" media="print" href="print.css" /> <!--[if !IE]><!--> <link rel="stylesheet" type="text/css" media="handheld,only screen and (max-device-width:480px)" href="handheld.css" /> <link rel="stylesheet" title="Modern" media="screen and (min-device-width:481px)" type="text/css" href="data:text/css,@import%20'http://fqdn/modern.css';" /> <link rel="alternate stylesheet" title="Basic" type="text/css" href="basic.css" /> <!--<![endif]--> <!--[if gt IE 7]><link rel="stylesheet" type="text/css" href="modern.css" /><![endif]--> <!--[if lt IE 8]><link rel="stylesheet" type="text/css" href="basic.css" /><![endif]--> <link rel="stylesheet" type="text/css" href="common.css" />

This works fairly well, at least with ancient browsers, Opera Mini/Mobile, and Webkit-based ones; but there are least two mobile browsers that take issue: the BlackBerry browser (before version 5) and NetFront. Different versions of each will either just get the common styles, or both the modern and common ones. There isn't much we can do about this, unfortunately, without server-side sniffing. That's because although we can use JavaScript to write another link, the BlackBerry browser ships with scripting off by default. So this kind of solution won't work consistently:

<link rel="stylesheet" type="text/css" media="print" href="print.css" /> <!--[if !IE]><!--> <script type="text/javascript">   if (screen && screen.width && screen.height && (screen.width < 480 || screen.height < 480))   document.writeln('<' + 'link rel="stylesheet" title="Handheld" type="text/css" href="handheld.css"><' + '/link>'); </script> <link rel="stylesheet" type="text/css" media="handheld,only screen and (max-device-width:380px)" href="handheld.css" /> <link rel="stylesheet" title="Modern" media="screen and (min-device-width:481px)" type="text/css" href="data:text/css,@import%20'http://fqdn/modern.css';" /> <link rel="alternate stylesheet" title="Basic" type="text/css" href="basic.css" /> <!--<![endif]--> <!--[if gt IE 7]><link rel="stylesheet" type="text/css" href="modern.css" /><![endif]--> <!--[if lt IE 8]><link rel="stylesheet" type="text/css" href="basic.css" /><![endif]--> <link rel="stylesheet" type="text/css" href="common.css" />

The basic trick

To summarize, you just have to model your solution on something like the following.

<link rel="stylesheet" type="text/css" media="print" href="print.css" />
<!--[if !IE]><!-->
<link rel="stylesheet" type="text/css" media="handheld,only screen and (max-device-width:480px)" href="handheld.css" />
<link rel="stylesheet" title="Modern" media="screen and (min-device-width:481px)" type="text/css" href="data:text/css,@import%20'http://fqdn/modern.css';" />
<link rel="alternate stylesheet" title="Basic" type="text/css" href="basic.css" />
<!--<![endif]-->
<!--[if gt IE 7]><link rel="stylesheet" type="text/css" href="modern.css" /><![endif]-->
<!--[if lt IE 8]><link rel="stylesheet" type="text/css" href="basic.css" /><![endif]-->
<link rel="stylesheet" type="text/css" href="common.css" />

You can see it in action on this minimal test page. It works consistently on all but certain mobile browsers and truly obsolete and disused ones, without relying on any fragile hackery.