CSS Not([hacks])

CSS Not([hacks])

Got something to say?

Share your comments on this topic with other web professionals

In: Articles

By Brian Suda

Published on January 7, 2008

As any web developer worth his salt will know, browsers can differ in their interpretation of CSS rules and properties. One way of coping with this headache is to use various hacks; they might (in some cases) be invalid CSS, but they force browsers to read only certain parts of your CSS and render your page or web site as close to how you intended as possible. CSS hacks are one of the common ways to send specific instructions to different browsers, be it to solve min-width issues or box model interpretations.

But there is another way. I will demonstrate the possibilities of using CSS3 selectors as an alternative to hacks when targeting specific browsers. Hacks are an example of graceful degradation: you design for the most capable browser and then fix issues in legacy browsers. But what if we took the opposite approach and worked with progressive enhancement?

In this case, progressive enhancement involves writing the CSS so it works in all your target browsers, and then adding enhancements for those browsers that better support the CSS specification. You can think of it as the reverse of graceful degradation.

Let’s not mince words here: most of the problems stem from Internet Explorer’s (IE) rendering bugs. Consequently, many of the hacks relied on bugs in IE’s CSS parser—including its inability to correctly parse comments, leading underscores for properties or the box-model hack. The downside is that these hacks are specific to IE and can potentially break when a browser is upgraded.

#elem {    width: [IE width];    voice-family: ""}"";    voice-family:inherit;   width: [Other browser width]; }

An example of the box-model hack: IE’s CSS parser incorrectly finishes the declaration block at the ‘}’ in the voice-family value, and fails to parse the last two declarations.

Wouldn’t it be better if we could continue to write CSS with statements that older browsers can’t understand, but are still valid? Using the child selector (‘>‘), for example, we can hide declaration blocks from IE6.

html>body p {   color: blue; }

An example of a child selector: all versions of IE up to and including IE6 couldn’t understand this rule, but IE7 does. You can use it to progressively enhance your designs. Newer browsers begin to gain the benefits stated in those rules without you having to constantly update your CSS.

This is a much better approach to take as it allows for progressive enhancement. As browsers become better at conforming to the CSS standard, the rules you already have in place begin to work. No need to wade through the old cruft of /* */ or _property looking for broken statements and updating the code if you write it well in the first place!

Using CSS3 for Fun and Profit

The easiest place to begin to look for valid CSS that some browsers understand and others don’t is the draft CSS3 specification. Portions of the specification are supported by Mozilla Firefox and Apple Safari—among others—but not IE.

Building a simple matrix of CSS statements and browsers, we can quickly see what features are and are not supported. Then we can offer some progressive enhancement: CSS rules that better conforming browsers support while worse or older browsers don’t.

Using the W3C CSS3 selector tests we can easily spot potential candidates for browser specific statements. I simply opened all the tests the W3C provides for CSS3 in 5 different browsers and looked for selectors that would not pass or fail unanimously. The table below shows a sample of the tests and their results.

CSS/Browser Opera 9.24 Internet Explorer 6 Internet Explorer 7 Safari WebKit 3 Mozilla Firefox 2
NEGATED More than one class selector Fail Fail Fail Pass Pass
:first-of-type pseudo-class Fail Fail Fail Pass Fail
:last-child pseudo-class Fail Fail Fail Fail Pass
:only-child pseudo-class Fail Fail Fail Fail Pass
:target pseudo-class Fail Fail Fail Pass Pass
:lang() pseudo-class Pass Fail Fail Pass Pass
:root() psuedo-class Fail Fail Fail Pass Pass

A quick look at the list reveals that the not() selector is supported by Firefox and Safari but not by either IE (version 6 or 7) or Opera. The lang() selector is supported by Opera, Safari and Firefox, but not IE. Finally, using only-child or last-child, we can target just Firefox. With these selectors we can now create browser-specific CSS statements without having to resort to errors in CSS parsing between implementations.

So we have a CSS-only method for browser-specific styling that doesn’t resort to hacks. Instead of using comment hacks, the underscore hack or the box-model hack, you can write valid CSS and still achieve some browser specific styling: by first setting a /files/includes/default.css case and then applying the more specific statements to browsers that can understand.

Putting it all together

Let’s illustrate this with an example. Suppose we have an image we want to hide in IE but make visible in other browsers. First, the HTML code:

<p id="gravatar">   <img alt="My gravatar" src="data:image/png;base64,iVBORw0KGg...==" class="photo"/> </p>

Second, we need to declare our base-case to hide the image in all browsers:

#gravatar img.photo {   display: none; }

This will hide all img elements with a class ‘photo’ that are descendants of the element with the id ‘gravatar’.

Having done that we need to then display the image for those browsers that have better CSS support.

#gravatar img.photo:not([border]) {   display: block; }

By adding the not() selector and checking for a border attribute, we can re-display this image for browsers that understand this statement. It specifies (take a deep breath) that all img elements with a class ‘photo’ that are descendants of the element with the id ‘gravatar’ and that do not have a border attribute should be displayed as a block element. Effectively, this makes the img element visible again. If we look at the browser matrix in the table above we see that only Firefox and Safari support this. We now have a way to specifically target just those browsers.

Using the not() trick will allow you to display the image (or anything else that takes your fancy) to a subset of browsers. Both IE6 and IE7 as well as Opera don’t understand this CSS statement, so in those browsers the image will remain hidden. But what if we want to display the image in Opera too, without affecting IE?

To accomplish this, we need look at the browser matrix and see which selectors will work in Opera, Firefox and Safari, but not the IEs. The :lang() pseudo-class looks like an ideal candidate; we can change the CSS statement to:

html:lang(en) #gravatar img.photo {    display: block; }

(Note for this to work you must include a lang="en" attribute on the html element.)

This is basic progressive enhancement. When the next version of Internet Explorer arrives with (we hope) better CSS support, it will pick up on the rules targeting Firefox, Safari, and Opera, and it will display the progressively-enhanced page. And when this happens you won’t have to worry about updating your CSS files: the information you added was never a hack in the first place. This is assuming that when IE8 supports the :lang() pseudo selector it will also be capable of displaying the image.

Using a pure CSS solution is always preferable to using hacks. Even Microsoft recommends avoiding hacks for IE, instead advocating the use of conditional comments. On the web, as in life, my mantra is “everything in moderation”. I have hopefully demonstrated to you that it is possible to write CSS for different browsers by using CSS3 selectors, therefore keeping your hackery to a minimum.

Further reading

Related Topics: XHTML, Web Standards, HTML, CSS, Browsers

Since discovering the Internet in the mid-90s, Brian Suda has spent a good portion of each day connected to it. His own little patch of Internet is http://suda.co.uk, where many of his past projects and crazy ideas can be found.