Cross-Browser Debugging CSS

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.

Display

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.

Positioning

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:

  1. Do these properties go with the display and positioning types I have chosen?
  2. 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.

Internet Explorer

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.

return result+4;

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.

Implementing solutions

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:

  1. Rely on the cascade
  2. Use vendor prefixes
  3. Use * and _ hacks for IE6 & 7
  4. Almost never use \9 for IE8
  5. Know when to quit trying to hack IE
  6. 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:

.foo{
  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:

.clearfix {
  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.

49 thoughts on “Cross-Browser Debugging CSS”

  1. Favourite IE bug:

    When using filters ( alpha, for example ) on @font-faced elements, anti-aliasing gets disabled – until you add either background color or background image to the element.

  2. Thanks for this great article! I found it very useful.

    I don’t like any of the IE bugs, so I don’t have a favourite. But my favourite way of dealing with them is to target IE6 only by using the star html “hack” like this:

    * html .selector {IE6 specific styles go here}

    .. and targeting IE7 by

    * +html .selector {IE7 specific styles go here}

    I find that to be cleaner as the “hacks” are more easily spotted throughout the code. I like to filter out all IE (6 and 7, at least) hacks as separate selectors. One can still rely on the cascade, of course.

  3. Great article. If there is a particularly frustrating layout bug, I start to strip whole blocks out by either commenting out the code, or just deleting using the Inspector. This way, I can identify the exact block of HTML that is causing the problem and debug from there. It’s easy the spend vast amounts of time trying to fix a small bug, so I find this is generally the fastest way to narrow my search for the problem.

  4. Thats a great piece, I think ill point junior designers/developers to this post. It will give them that “oooooooooohh thats why….” feeling :)

    Also interesting how you would use css hacks in your main css doc as opposed to having separate css docs for each IE browser. Makes sense, I think the down side to boilerplates HTML files is they teach us only one way of developing.

  5. Thanks for the article Nicole,

    Jslint link is not working on point 1 HTML interpretation.

    Is Opera browser not a good choice? Please advice as you did not mention…

    I work here http://www.ziggo.nl as a front-end developer.

    Cheers,

    EP

  6. Hi!

    That’s a great article! I have the same philosophy when I code in CSS (and debug it on IE)

    Let me add a few remarks:

    You made a typo in “Almost never use /9 for IE8″ (the hack is “\9″)

    But in fact, you can remove the “almost”: \9 or / doesn’t target only IE8 and lower versions, but also IE9 in most cases.

    (I suggest you google “css hack IE9″ for more info, or test it by yourself)

    That’s why conditionnal classes are a better solution IMO.
    (http://paulirish.com/2008/conditional-stylesheets-vs-css-hacks-answer-neither/ for more info)

    And to answer your question, my “favourite” IE bugs are the min-height ( fixed by “_height” for IE6), and max-height, min-width, max-width that need CSS expressions on IE < 8.

    Three other IE bugs are less frequent but funny, "double margin", "peekaboo" and "unscrollable content" (detailed and fixed by this IE7.js demo: http://ie7-js.googlecode.com/svn/test/index.html )

    Cheers,

    Max

  7. Gotta love the “Know when to quit trying to hack IE”. Shame that many corporate clients (banks above all of them) don’t get this when it’s about cosmetic issues that don’t affect the user experience!

  8. Excellent article!

    “That means that when you use multiple hacks you need to put them in order; underscore, then star, then \9.”

    I thought it would be the other way around: \9, then star, then underscore. If a *hack comes after an _hack, then IE6 will use the *hack value.

  9. Quirksmode.org is a great, classic resource, and PPK is is of course a god.

    But caniuse.com has become my go-to resource to check on browser support for various CSS3/HTML5 technologies. The interface is fantastic, and PPK has been focusing more on mobile lately, anyway.

  10. My favorite IE6-Bug, which took me near insanity, was the box-height, which could not be smaller than the calculated line-height. Until today I didn’t found a possibility to create a block which is smaller than 3px.

  11. I forgot to say… if I end up with a problem I can’t find the solution to, I’ll go back and rebuild the component to see if I either end up with the same bug again (in which case I’ll understand it better), or perhaps find that some detail shifted and it no longer has the same issue.

    For the list of hacks, I used Paul Irish’s CSS hacks list.

    @Alan – good point re: Can I use?. For some reason it doesn’t have a lot of google karma so I haven’t switched over, but it is an amazing resource.

  12. Great thoughts on approaching CSS debugging. I think a lot of beginners struggle on the block vs. inline element differences.

    I would however recommend not using * and _ hacks for IE6 & 7 but instead use conditional comments to add an .ie6, .ie7, .ie8 class to your HTML or Body tag. Then you can have a valid stylesheet that is not as messy and just use those IE classes to target those specific browser versions to override a style.

    Paul Irish popularized the concept and he has some other great reasons for why this is better and cleaner: http://paulirish.com/2008/conditional-stylesheets-vs-css-hacks-answer-neither/

  13. Not just ‘position relative ancestor’. Any positioned ancestor that isn’t static.

    An absolutely positioned block within another absolutely positioned block is positioned relative to that, not to a relative ancestor.

  14. Is that ‘-webkit-gradient(linear, …)’ line really still necessary? Chrome has switched to the ‘-webkit-linear-gradient(…)’ syntax quite some time ago, and Safari has followed.

    However, the unprefixed value is not correct. It should be ‘linear-gradient(to bottom, …)’ (or just without ‘to bottom,’ in this case since this is the default); the spec has been changed. http://www.w3.org/TR/css3-images/#linear-gradients

    Firefox has implemented the spec-conform syntax as ‘-moz-linear-gradient(to bottom, …)’ already – with “magic corners”. http://meyerweb.com/eric/thoughts/2012/04/26/lineargradient-keywords/

  15. “3px float bug” ? What’s that? Do you mean the “double float-margin bug” ?

  16. Hi !

    Very nice article. I would have loved to read this when I started playing around with HTML and CSS. It would probably have avoided me to get nuts a bunch of times. :D
    It took me a long time to figure out how the box-model actually works.

    As far as I can tell, I don’t have to use specific CSS hacks since then. Maybe because I don’t go really far into the oldest versions of each browser (who says IE6 ?).

    I think the thing that pisses me off the most about IE is the difference between browser mode and document mode which can litterally drives anyone not aware about that crazy.

    Cya !

  17. Thanks for the article. I’ve just come back from a long break from coding and designing, and I’m just wondering why you wouldn’t first code for IE9? If I’ve understood correctly, IE9 has the CSS2 spec down pat (but not CSS3). I thought it would have made more sense to get the basics right in IE9 and then move to Chrome to build out the CSS3 stuff.

  18. I think it is highly important that people realize there is no need for things to look exactly the same in every browser. Ensuring that code is valid and semantic is most important first (for functionality) and then make sure to apply the bells and whistles to the browsers that support it. Fixing it so that it’ll also work for the lesser browsers will just encourage their use.

    I used to struggle with the same thing and one day came to realize that no one is going to die if IE6 wasn’t displaying rounded corners and beautiful little box or text shadows.

  19. Great article. I follow a similar workflow but I have never had the need to use ZOOM:1 on IE. Why should one use it or in which cases?

  20. Hmm…

    Whilst there’s some good tips, for example: “finding out why IE is interpreting something different as opposed to hacking it”, I’m concerned about the tip to use * and _ to hack IE 6 and 7. And in all honesty, who has to hack for IE9? I’ve had my issues with IE over the years, but I’ve yet to have any issue with IE9.

    I’ve never been keen on this approach, partly because of validation, but mainly because * & _ always seems to get buried amongst the regular code; which makes it harder to find if you – or anyone else – needs to return to it. If I do have to override declarations for IE, I’d suggest a conditional html tag, or an IE conditional stylesheet; depending on how much IE stuff there is to fix. A conditional html tag overrides because of specificity and conditional CSS would override due to the cascade…both is more ideal than just hacks within the code.

  21. “Inline elements have line-height, vertical align, and can also be whitespace sensitive. Margins, paddings, heights, and widths aren’t appropriate. ”

    Horizontal margin and padding do apply to inline elements and also have effect on their sides. Hope that helps!

  22. “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.”

    Could we see those debug stylesheets? Thanks for this article, Nicole. Very useful.

  23. Nice article. To be really effective with CSS, I use Compass – http://compass-style.org/ – and if I ever need specific IE rules, I go with something like that:

    The most important thing is the markup: a nice, clean and clever markup will save you hours.

  24. Nice summary of the debugging process.

    Do you think you might write a future post about your aforementioned debug stylesheet?

    Thanks for the great article.

  25. Nicole,
    Great points about hacks and thanks for the display and positioning stuff. I think too many CSS devs are missing that basic knowledge. I know it took me a couple of years to really get it.

    It is refreshing to see articles on your site about the real world problems we find in larger sites. It makes me feel a little more comfortable talking “crazy” about where I think the standards and best practices do not serve the enterprise sites. Keep it up!

  26. Great Article!
    Zoom:1, as suggested in the article, is typically used to trigger the hasLayout behaviour in IE, for instance, I once applied a filter gradient (against this article recommendation) to a div background but didn’t show up.

    filter: progid:dximagetransform.microsoft.gradient(startColorstr=’#E0E0E0′, endColorstr=’#CCCCCC’);

    I had to add the zoom:1 rule to imply that the div is visible in IE7. Not convinced? further reading: http://www.satzansatz.de/cssd/onhavinglayout.html

  27. Great article indeed. There were some bugs in this list that affects IE (7 &8) in ways we -still- haven´t ran into yet. Will get into consideration zoom:1 and the _ and * hacks…. Really hate that extra mile that IE makes us to walk, sometimes on our knees. :)
    Also have to agree that, when bending css core rules, things goes awry. Sometimes when trying to make some block (background color) to “fill” the general layout, with blocks inside, we (have to admit) used display:table with a #generalcontainer or #wrapper…. and the color filled the background. But browser issues arised … Again, must watch out carefully CSS layout approaches right from the start: at latter stages fixes can get extremely painful/dangerous/ time-consuming/uneffective.
    In the same tone, we usually run into problems with webkit browsers, and (put the blame on us) we reserve the last lines of code to target that chrome/safari issues, with this code:

    @media screen and (-webkit-min-device-pixel-ratio:0)
    {
    .class{values}
    #id{values}
    and so on… with the affected elements that display wrongly in Safari/Chrome…
    }

    Obviously we get that after many google searches. But again, watching good css practices seems to be the best cure to cross browser issues. Thanks a lot Nicole, will RT this!

  28. Block level elements should be used with margins, paddings, height and width. Line-height isn’t appropriate.

    Line-height is definitely appropriate. If the block element contains text, how would you otherwise define the distance between lines?

  29. Great post, Nicole. I’m happy to know that I have been following these principles and I’m not the only one. :) *wave*

  30. Nicole,

    I always used to debug HTML/CSS in IE first because that’s the predominant browser for many of the sites I work on but then I started relying more on Firebug and Chrome and would try and fix IE afterwards. It felt good but wrong at the same time. Like eating ice cream for breakfast. It’s reassuring to hear someone more experienced with CSS recommending that approach.

    One of the things I always wind up doing when I’m debugging CSS for IE is to add redundant attributes. For example something that starts off as: <div class=something><div class=else /></div>
    .something {margin: 0; padding: 0} .else { font-weight: bold }
    turns into
    .something {margin: 0; padding: 0} .something .else { margin: 0; padding: 0; font-weight: bold;}
    which doesn’t make a difference but adds extra chars and I forget to delete them when I finally find the issue. The issue tends to be the ordering of the declarations but that’s another problem.

    Do you know of any tools that will identify those redundancies and strip them out? I use YUI Compressor but the documentation doesn’t indicate that it does anything like that.

  31. I always expand on the HTML5 boilerplate model to target IE versions, rather than using hacks in my otherwise nice CSS.

    So I simply use conditional comments to apply classes like ‘.lt-IE8′, then when I need a ‘hack’ on a class, I simply precede it with ‘.lt-IE8′ and apply the hack there. This is nice, as you won’t bother browsers that actually behave nicely (I know, they ignore hacks, but still).

  32. Hello, Nicole!

    First of all, I want to tell you that I agree with what you’re advocating for, and the things you say are guidelines for me :). I also appreciate you sharing with us your knowledge.

    As for the current article, my preferred method of targeting specific browsers in order to fix layout bugs is to add from server-side code (PHP is my choice) to the body element, the classes I need. For example, if I need to target IE 6, 7 and 8, I add the class “ie8-” (which stands for ‘IE 8 and below’) or “ie6-7-8″. If I need to target Safari I add the class “safari”, or “mac-safari” for Safari on Mac OS X particularly, if I need to.

    Experimenting with multiple methods, I think this one is the best if I don’t have a lot of targeted code, because I don’t have to scatter a bunch of hacks in my CSS, and I have cleaner code, and easier to understand.

    Another good choice could be, depending on how much targeted code there is, to include specific CSS files for one browser or another. This is better if there is a lot of targeted code for a browser, because a browser that doesn’t need that code will not download it unnecessarly. For example I could have the main style.css and other style-ie8-.css or style-ie-6-7.css. And whether I need to include one of these files or not, it’s my PHP logic deciding.

  33. My absolute favorite is the limit that IE has on the number of CSS files it will download, as well as the max number of selectors inside a single css file.
    I faintly remember something about the max size of a CSS file as well.

  34. Nice post, I am a little confused though on one item – You say paddings and margins are not appropriate on inline elements but what if you display inline a list item?? how would you then give space/layout to the lists?

    Cheers
    Jamie

  35. Great article. I agree that there is a point when you need to stop using resources to make elements uniform on IE browsers. Once I get my designs functioning and looking at least decent in IE, I move on.

Comments are closed.