Understanding how browsers parse CSS and render websites is an important first step towards writing more efficient code. In this first piece on efficient CSS, we look at selectors, how the browser reads them, and how to make yours more efficient–plus a few tips on how to include stylesheets in your projects.
How the Browser Reads Selectors
The foundation of writing efficient CSS is knowing how browsers parse selectors. The common assumption is that CSS is parsed from left-to-right just as we read English. In reality, browsers use bottom-up parsing for enhanced efficiency, which in plain language means your selectors are read from right-to-left.
Consider the following:
#menu ul li a#current {…}
The browser first checks for #current, then a, then li, then ul, and then #menu. This is inefficient because the browser found the element it was after on the first check, but was then forced to continue looking through the document for all the additional selectors.
In CSS, IDs (denoted by the hash) are always unique elements on the page, so whether or not the browser reads left-to-right or right-to-left, the selectors #menu ul li a are redundant anyway.
The most efficient selector usage would simply be:
#current {…}
If the browser can determine the element you are looking for using just one selector and you’ve used more, you have inefficient code on your hands.
You should aim never to use more than two selectors to identify a rule. While going overboard by creating useless selectors in your markup is also inefficient, it’s still better to add additional IDs and classes in your markup than to require extensive drilling in your CSS to find the right element.
Selector Type Efficiency
There are four main types of selectors in CSS–ID, class, tag and universal.
- IDs are unique elements denoted by the hash, like our old friend #current.
- Classes are reusable elements denoted by a period, for example: .comment.
- Tags denote HTML elements such as lists, links and so on, for example ul, li and a.
- Universal refers to tags that target every element on the page, such as *.
Because CSS is rendered by following a bottom-up parse, the most efficient coding targets the most specific element first (and by first, we mean the furthest-right selector). Our last example had an unordered list item within another element, presumably a div:
#menu ul li a#current {…}
Most CSS you’ll see follows a similar pattern. Someone who wants to target a single list item in the div #menu will you use #menu ul li. This rule will look for every li element on the page, narrow it down to every li element inside an unordered list, and then look for the list items inside unordered lists that are in #menu.
On the other hand, this rule will render much more quickly:
li ul#menu {…}
First, the browser isolates the #menu element, presumably a div, and only continues further checks that fall within that particular cascade. It selects the unordered lists next, ruling out the ordered lists before it looks for each list item. If you have ordered lists inside #menu, this is faster than checking all list items first and then only applying the style to those within unordered lists. Conversely, if your sidebar uses only unordered lists, you can sacrifice some specificity for the sake of efficiency:
li#menu {…}
Probably the biggest culprit for slow rendering efficiency on websites today is the poor use of selectors–we all do it, and it’s not a subject many people spare a second thought for. But we should, and if you can get your head around the way browsers read CSS, you can improve your load times.
There are plenty of arguments that it would better to just give those list items their own class, preventing something called tag-qualifying, but how far you go to reduce the number of selectors the browser needs to parse is up to you. I would say that checking for li#menu over something like .menu-list-item is worth the tiny efficiency hit in that it leads to less markup maintenance.
Before we move on, it’s worth mentioning that not only is there pretty much no reason to use universal selectors, they are incredibly inefficient. They tell the browser to check every single element on the page, which is a crazy thing to do. Unless you have a great reason, you never want to see a rule like this in your stylesheets:
body * {…}
Don’t use @import or inline styles
The browser doesn’t render a webpage until all stylesheets have been downloaded. While using @import rules may keep the number of stylesheets listed between your head tags to a minimum, it also causes each stylesheet to be downloaded sequentially rather than simultaneously.
Use the <link> tag to include all necessary stylesheets in a page — never use @import in your ‘main’ stylesheet to include the rest. Always make sure that your stylesheet links are the first things within the head tags after the title. Nothing renders until the stylesheets are ready, so this is key to improving load times.
Inline styles also cause the page to move around as it is rendering because the browser doesn’t have all style information ready before it begins. Either move your inline styles out of the body and put them between the head tags, or better yet, don’t use inline styles. CSS should go in CSS files.
There’s usually a good reason to have multiple stylesheets for different purposes, but don’t go overboard. Wherever possible, reduce the number of stylesheets that need to be downloaded. Even though they’ll all be downloading simultaneously when you link them between your HTML document’s head tags, it’s better to have a slightly larger file that triggers one HTTP request as opposed to many HTTP requests for multiple small files. Most sites load slowly because there are too many HTTP requests to be made as opposed to sheer file size, hence why sprites are so popular.
The Bottom Line
Internet speeds and computers themselves are faster now than ever. That doesn’t mean we should ignore efficiency — users get more and more impatient with load times every single year, and the longer it takes, the more likely it is someone just closed your site before they even got to see anything.
But we can strike a medium based on personal preference. Don’t ignore efficiency altogether, but consider how far you can go before it starts to impact your ability to read your own code and to code quickly. Earlier in the piece, I suggested a pretty efficient method over an even more efficient method for this reason.
Learn these rules by heart, apply them where you can, but don’t go too far in either direction. It’s best to err on the side of efficiency if you’re unsure, but don’t gimp yourself or your time with unmanageable code.
Get the TNW newsletter
Get the most important tech news in your inbox each week.