Tuesday, January 22, 2013

How Much CSS Should You Have?

I've noticed recently that it's becoming more and more common to see websites with WAY too much CSS.   I realized that I've never seen specific guidelines around how much CSS a website should have, and no clear narrative around the impact of total CSS transfer size.  Most people just say "as little as possible" or "just write what you need and then optimize".  On the whole I think these statements are a bit too vague to be useful.  In addition, there are a lot of articles about "optimizing" CSS (a.k.a  writing more efficient selectors), which has quickly diminishing returns.  In this article I will talk specifically about how big your CSS files should be, and talk about some other possible pitfalls when authoring CSS.


The Bytes Matter Most

According to the HTTP Archive, the average website has 35KB of CSS, and CSS transfer size has the highest correlation to render time.  If you look at the graph directly above the one for render time at that link, you will also notice that total transfer size has the highest correlation to page load time.  This makes the case for reducing the overall weight of your CSS pretty strong.  Based on this data and what I've seen in my own work, you should shoot for less than 30KB of CSS on each page of your site (after gzip).  It is true that you should really try to drive this number as low as possible (without wasting your life trying to remove a couple of bytes), but if you have more than 30KB of CSS then it is likely that there is room for improvement with minimal effort.

What About Efficient Selectors?

This is one of the recommendations from the "Make the Web Faster" team at Google, but people often take it too far.  It is true that really bad selectors can torpedo page performance, but it is typically not worth it to go through and optimize every selector.  As long as you are avoiding the universal selector (*) and your DOM is a reasonable size (~2000 elements or fewer), you are better off focusing on removing CSS instead of optimizing selectors.  Deleting unnecessary CSS also has the added benefit of making your code more readable and maintainable.

How To Reduce CSS Size

I am assuming that you are minifying and gzipping your CSS, but if not then please go do that before reading any further.  Once that's done, I suggest watching this talk on the "Top 5 Mistakes of Massive CSS" from Velocity 2010 (video, slides).  I don't want to rehash the entire talk here, but in the interest of saving you some time the five mistakes are:
  1. To much granularity in your CSS (new CSS classes for every little thing)
  2. Stale rules (obsolete CSS)
  3. Unpredictability (inconsistent cascade causes people to write one-off solutions)
  4. Specificity wars (writing extra CSS to override inherited rules)
  5. Duplication (tons of margins/floats/font-sizes)
The talk goes into much more detail on each of these points, so I highly recommend at least flipping through the slides.  I really want to cover a different topic here, which is a new problem that I've seen crop up in recent months: improper use of CSS preprocessors. 

In case you aren't familiar with these tools, the website for LESS (one of the most popular preprocessors at the time of this writing) gives a good overview their benefits.   Unfortunately, it also clearly illustrates one of the dangers of using a preprocessor directly in its documentation.  Take a look at this example code:


This is great right?  Look at how easy it is to use the rounded-corners mixin!  In this example it looks like a boon for maintainability, without a huge bloat in the amount of CSS you have.  Unfortunately, this example is only using the mixin twice.  In a real world scenario, you might have a dozen instances of this mixin, each one generating 5 lines of CSS.  Multiply this by a few dozen mixins, and a basic LESS file that is only a few hundred lines could easily turn into an 80KB CSS file once it is compiled.  This problem could be avoided by using the OOCSS pattern and reusing the same CSS objects instead of repeating the same CSS declarations all over your stylesheet.

Let me be clear, I'm not against CSS preprocessors.  I think they have their place and can be extremely useful.  I just think that it's important to be wary of automatically generated CSS and the unexpected bloat that it can cause.  For example...

Basecamp's CSS

Basecamp is a piece of project management software that I've used in the past, and overall I think it's a good product.  The company behind it, 37Signals, has a popular blog where they have talked about their use of SCSS to speed up development.  I read the post and figured I would look at their compiled CSS (which they serve to every visitor) to see what it looks like.  For starters I ran a test via WebPagetest to get a sense of the size of their CSS:



271K of CSS?!  Holy crap!  That's almost eight times the average from the HTTP Archive.  When we look at the request for this CSS file we can see that the content download took over two seconds and blocked the rest of the page from downloading during that time.


If this doesn't make the case for a smaller CSS file, I don't think anything will.  I'm assuming that this contains all the CSS you would need to use the full version of Basecamp (since Basecamp is a one page app), but even so it's a huge file.  Let's look at it and see if we can figure out why it's so large...

The first thing that's obvious is that the file is not minified.  This is an easy fix, but according to PageSpeed it would only save about 4KB.  Next I ran it through CSS Lint, which turned up a ton of "Disallow overqualified elements" warnings, and when you look at the CSS it is easy to see that this is causing a lot of duplication:

body.extras section.apps article a.rightsignature {
    background: url("/assets/images/extras/rightsignature-x2.png") no-repeat;
    background-size: 60px; 
}
body.extras section.apps article a.dashstack {
    background: url("/assets/images/extras/dashstack-x2.png") no-repeat;
    background-size: 60px; 
}
body.extras section.apps article a.slickplan {
    background: url("/assets/images/extras/slickplan-x2.png") no-repeat;
    background-size: 60px; 
}
body.extras section.apps article a.quoteroller {
    background: url("/assets/images/extras/quoteroller-x2.png") no-repeat;
    background-size: 60px; 
}
body.extras section.apps article a.everest {
    background: url("/assets/images/extras/everest-x2.png") no-repeat;
    background-size: 60px; 
}
body.extras section.apps article a.feeds {
    background: url("/assets/images/extras/feeds-x2.png") no-repeat;
    background-size: 60px; 
}
... about 30 more of these ...

This is sometimes a symptom of using a preprocessor, and there's a lot of opportunity to refactor this CSS to reduce the duplication.  The "background-size" declaration and the "no-repeat" component value are repeated in every rule, so it makes sense to factor them out.  This pattern of repetition is common throughout this stylesheet, which is the main reason why it is so large.

There are other anti-patterns in this file, but I'll leave it as an exercise to the reader to identify them, and trust that you will believe me when I say that if you have 271KB of CSS after gzip then something is very wrong.

Recap

Total CSS size can have a huge impact on performance, and that's where you should focus your energy as opposed to optimizing individual selectors.  You should target something like 30KB of CSS on each page, and smaller is always better.  To get your CSS that lean you should start by leveraging the techniques that Nicole talked about at Velocity a couple of years ago.  If you are using a CSS preprocessor like LESS, then you should be extremely careful about what your mixins look like, and you should take a long look at the compiled CSS to ensure that you aren't introducing unexpected duplication.

Hopefully by following these tips you can have a faster site, more maintainable CSS, and happier users!