I was helping Laura (a developer who works with me) learn about cross-browser debugging this week, which got me excited to share my process.
The first principal is simply:
Work with CSS, not against it.
CSS has an underlying design and when you work with it, with the natural flow of how CSS is meant to be used, you will find you have a lot less bugs. I learned CSS by reading the W3C specifications, which is why I began coding according to the language’s design, but however you learned it, you can pick up some of the key points involved.
The first thing I do is code to a good browser from the start. Our choice is Google Chrome, mainly because of the superior developer tools. When I have something working in Chrome and I am satisfied with it, I take a look at it in either Safari or Firefox.
If there is a discrepancy between these good browsers, chances are you are working against CSS. Do not try to hack around discrepancies between good browsers. Your goal is to figure out *why* it is being interpreted differently. Usually there is a very good reason. These are some of the things I check on if I have bugs:
- HTML interpretation – did you forget to close a tag? Did you wrap an inline element around a block level element? Anything that veers off the standard will be interpreted differently by different browsers.
- Run your CSS through CSS lint. It will give you a good sense of any errors (missing semi colon?) that might be throwing you off. For debugging cross browser differences, the errors are more interesting than the warnings.
- Forgot to use a reset/normalize stylesheet and are relying on (different) browser default styles.
- Browser support differences. Are you using advanced CSS3 properties or HTML5 elements? Check browser support to be sure all your target browsers are covered (quirksmode is where I usually do this). If not, you may still be able to use the fancy-pants properties, you’ll just need to design clever fall-backs for the clunkier browsers. For example, borders instead of drop shadows or square instead of rounded.
- Margins are not being trapped correctly. If you have weird spaces in unexpected places, chances are your margins are collapsing in an undesirable way.
- You created a new formatting context in one browser, but not in another. Typically this happens as a result of overzealous use of the zoom:1 property to trigger hasLayout in IE. Yes, hasLayout is essentially the same thing as a new formatting context in better browsers.
- Using absolute position, without setting horizontal and vertical offset. For that reason, the absolutely positioned element will have the same position it would have had when set statically. However, if you try to manipulate the top, right, bottom, or left values, the element will all of the sudden be positioned relative to the nearest relatively positioned ancestor causing it to jump.
- Did you combine display types in unexpected ways? For example, the spec doesn’t lay out what happens when a table-cell is next to a floated element without a table row or table in between. It doesn’t mean you can’t do it, but it does mean that if you do, you open yourself up to potential bugs and will need to spend more time cross browser testing.
- Is whitespace affecting your layout? You almost never want to have whitespace dependencies on your CSS display, but sometimes it happens, particularly with display inline and inline block and with images since they look like blocks, but, unless you set them all to display block, don’t behave that way.
- Rounding errors can cause display differences. All flexible layouts and grids have to deal with sub-pixel rounding errors, they each find different ways to minimize the visibility of these differences.
If you don’t read anything else, read the next two paragraphs
The most important thing to keep in mind is that error behavior is not defined in the spec. If you go off-roading you don’t know what you will get. Chances are it will be different between browsers. If you are combining odd properties (like margins on an inline element), you will have cross browser differences.
I think of CSS like a choose your own adventure. When you have made certain choices, others become obvious. For example, you need to first choose your display type block, inline, inline-block, table. When you have chosen that, you are left with a tool-box of appropriate tools to use to alter the display. For example,
- Block level elements should be used with margins, paddings, height and width. Line-height isn’t appropriate.
- Inline elements have line-height, vertical align, and can also be whitespace sensitive. Margins, paddings, heights, and widths aren’t appropriate.
- Tables have vertical and horizontal alignment and can sometimes behave bizarrely if you have one element of a table without the others (e.g. a table-row with no table-cell). Margins are inappropriate for table-rows and table-cells. Padding is inappropriate for tables and table rows.
If you stick to the tool box that naturally goes with your display type, you will have far fewer bugs and cross-browser differences.
Next, if you chose block, you must choose your positioning mechanism. (The others are generally positioned according to the normal flow). So for blocks you can choose:
- Float – brings blocks all the way to the right or left. If you floated something, you made it a block level element, which means previously applied vertical-align or line-height properties may no longer work.
- Absolute – positions the element relative to it’s nearest position relative ancestor. Keep in mind that absolutely positioned elements do not trigger reflows and are not reflowed when ancestors and siblings are changed. This is a strength for animations, but can cause display issues if you use too much position absolute with dynamically updating content. (e.g. the old-school example is corners that do not move when more content is added to the box).
- Static – the default, this is how you get back to a standard element in the normal flow.
- Fixed – positions the block relative to the viewport. Rarely used.
- Relative – mostly doesn’t affect the node it is applied to, but children will get their absolute position relative to this node.
I’m not organized enough to enumerate all the display and positioning types and tell you which can be used/not used with which other properties, so you are going to have to think it through for yourself when debugging and writing code. There are two important things to consider:
- Do these properties go with the display and positioning types I have chosen?
- Do sibling positioning types go together?
For example, does it make sense to have a float, table-cell, and inline element interacting with each other? What should the browser do with that? Is it defined in the spec? If not, you are probably going against the grain of CSS. That can be ok sometimes, but you should know exactly why you chose to do it and leave extra time for cross browser testing.
When you have resolved all the discrepancies between the good browsers, you are ready to look at IE. I recommend starting with the oldest version of IE that you need to support because lots of bugs continue to exist in newer versions (in slightly modified form) so you will have less to fix if you start with the worst one.
Even with IE, you want to try to figure out why it is interpreting something differently rather than just hack around it. Adding * and _ hacks to your code willy-nilly is like finding out a function is returning the wrong value (say four less than it should be) and just adding the difference to it rather than figuring out where the math went wrong in the first place.
That said, it is ok and sometimes necessary to hack IE6 & 7. IE8 usually only needs hacks to accomodate a lack of support for modern CSS3. If you think you need to hack, try to figure out the exact bug you are dealing with. There are tons of resources for this online (anyone remember PIE?). The particular problems which require hacks in IE6 & 7 are:
- Needing to add hasLayout with zoom:1
- Position relative causing things to disappear
- 3px float bug
- Expanding container float bug (useful!) and overflow hidden which unfortunately “fixes” this useful bug.
- Do you have a favorite IE bug? I’d love to hear about it in the comments.
There are, of course others, but these are the few I’ve had to hack around for OOCSS. The others occur far less frequently, like the duplicated content bug when you have two floated elements with a comment in between. I don’t know how to explain figuring out IE bugs because, for the most part, I’ve internalized them. Like speaking a foreign language. The best I can suggest is to carefully examine what you can see and carefully craft your google search to describe it. Don’t start hacking until you identify the bug. The dev tools for IE are horrible, so you may need to use background colors to “see” the problems. I create debug stylesheets for that purpose.
When you have figured out what is wrong and you know how to solve it, you are ready to figure out how to put it in your code without breaking everything. Here is my process:
- Rely on the cascade
- Use vendor prefixes
- Use * and _ hacks for IE6 & 7
- Almost never use \9 for IE8
- Know when to quit trying to hack IE
- Never use hacks to target the latest versions of Firefox, Chrome, or Safari.
That’s a lot to take in, so I’ll detail each one below.
Rely on the Cascade
First, rely on the cascade whenever possible. CSS has a natural system of fallbacks built right in. Browsers take into account the last value that they were able to understand (this is how the cascade was designed to work). This means that if you order different solutions to the same problem from least advanced to most advanced, browsers will naturally use the most advanced solution they are capable of understanding. For example:
background-color: #ccc; /* older browsers will use this */
background-color: rgba(0,0,0,0.2); /* browsers that understand rgba will use this */
Use vendor prefixes
The next tool you want to employ is vendor prefixes. They allow you to give different values to different browsers, particularly for properties that haven’t stabilized.
background: #1e5799; /* Old browsers */
background: -moz-linear-gradient(top, #1e5799 0%, #2989d8 50%, #207cca 51%, #7db9e8 100%); /* FF3.6+ */
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#1e5799), color-stop(50%,#2989d8), color-stop(51%,#207cca), color-stop(100%,#7db9e8)); /* Chrome,Safari4+ */
background: -webkit-linear-gradient(top, #1e5799 0%,#2989d8 50%,#207cca 51%,#7db9e8 100%); /* Chrome10+,Safari5.1+ */
background: -o-linear-gradient(top, #1e5799 0%,#2989d8 50%,#207cca 51%,#7db9e8 100%); /* Opera 11.10+ */
background: -ms-linear-gradient(top, #1e5799 0%,#2989d8 50%,#207cca 51%,#7db9e8 100%); /* IE10+ */
background: linear-gradient(top, #1e5799 0%,#2989d8 50%,#207cca 51%,#7db9e8 100%); /* W3C */
Notice the two syntaxes for webkit. Just like normal property fallbacks, vendor prefixes should be ordered from oldest version to newest so that each browser gets the best code it is capable of handling.
If there is a standard syntax, you want to put that last so that as support for the standard increases, more and more browsers will use the best code. This is marked with a comment “W3C” in the above code.
Use * and _ hacks for IE6 & 7
When you have identified specific IE bugs, and you know how you want to work around them, use _ and * hacks to target that particular browser. For example:
overflow: hidden; /* new formatting context in better browsers */
*overflow: visible; /* protect IE7 and older from the overflow property */
*zoom: 1; /* give IE hasLayout, a new formatting context equivalent */
All IE hacks target a particular browser and everything before it, so for example:
- _ targets IE6 and older
- * targets IE7 and older, and
- \9 targets IE8 and older UPDATE: IE9 is also targeted for certain properties.
That means that when you use multiple hacks you need to put them in order; underscore, then star, then \9.
Almost never use \9 for IE8
That said, I have not really needed to use \9 for any browser quirks in IE8. I use it simply to fill in the gaps when there is a difference in support. For example, if I’m using a box-shadow in better browsers, and the box looks weird without anything around it in IE8, I’ll use \9 to add a border for that browser. The cascading technique wouldn’t work in this case, because the backup method is applied to a different property.
Know when to quit trying to hack IE
Don’t try to make everything exactly the same in IE. It is important to think about what is in the best interest of each set of users. Do you waste another several HTTP requests, extra HTML, JS, and additional CSS to force rounded corners to work in IE6-8? For me, the answer is a clear “No.”
It is important to know when to give up on a particular feature. For example, don’t use filters to simulate css3 gradients. They cause performance issues and layout bugs. It is best to avoid the desire to make your site exactly the same in every single browser regardless of capability. Users of IE 6-8 are much better off with a simplified experience (not broken, just simpler) that with a site that rolls out all the bells and whistles using a ton of polyfills, but is incredibly slow.
For example, avoid the following code to duplicate the gradient from the example above.
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#1e5799', endColorstr='#7db9e8',GradientType=0 ); /* IE6-9 */
Never use hacks to target the latest versions of Firefox, Chrome, or Safari.
Finally, if you think you need to hack to serve different code to Firefox, Chrome, or Safari — something has gone very awry. It makes sense to go back and look at what you’ve written to see if you are going against the grain of CSS.
Do you have particular techniques for debugging CSS? Or for implementing fixes once you’ve found a solution? I’d love to hear more about it in the comments.
If you would like to read this post in spanish, click here.