Because they are not going to have a massive impact on performance, stylesheet-optimizations should always be motivated by readability and maintainability first. There are a few simple rules on how to write CSS that benefit these areas as well as performance without requiring major changes to your current workflow.
Any benefit you will see after following these rules is likely primarily due to ending up with smaller files. Consider the following a collection of “best practices” when it comes to writing not only efficient but also clean CSS.
The rule of thumb is to always use the simplest selector that matches, which can be achieved by applying these techniques:
If you are not sure how CSS gets applied to markup, you should first read my post on understanding CSS hierarchy-matching. It aims at giving a top-level overview essential for understanding the remaining recommendations.
Once you feel comfortable with this subject, you should see why the star selector (
*) should never be used: it forces the browser to evaluate every single element on the page without limitation.
Selectors are evaluated from right to left, so make sure that the rightmost selector in your chain is as specific as possible. This often completely removes the need for some or all of the other selectors in the chain, which should be dropped whenever they are not absolutely required.
Elements in a selector should be omitted if they try to mimic HTML too closely without increasing clarity. For example,
table tr td can be shortened to
td, because every table data-tag is going to appear in this manner. The same applies to all other types: use only what you need to unambiguously identify the element you are looking for.
Descendant selectors (spaces) usually perform worst because they require the entire hierarchy to be searched for all occurrences of the rightmost selector. For example,
p a forces a browser to search all links on a page before checking which of them is nested in a paragraph somewhere in its hierarchy. The more of these you add, the more loops will have to be executed to figure out if a selector does or does not match the element(s) found initially.
Child selectors (
>) perform slightly better than descendant selectors because they only have to check one additional layer of parental tags for all occurrences of the element on their right side, but should still only be used if they cannot be avoided.
Class- and ID-selectors match fastest and should be used whenever possible.
Never tag-qualify when using an ID since it already is the most explicit selector and only tag-qualify when using a class when unavoidable.
tag#id should always be
tag.class should be
.class if possible, meaning all cases where the given class is only ever found with the same tag.
Since an ID is unique on any given page, requiring it to appear in another unique element results in a wasted cycle of the rendering engine.
You can nest one ID in a page-level class (usually set on the
body-tag) if it behaves differently on different pages, but selectors should never contain multiple IDs. In most cases, you want to drop as many selectors as you can left of the rightmost ID:
.post #content header #author a should be
.post #author a or
#author a, depending on whether or not you need the page-level class.
The longer a selector is, the longer it will take the browser to match it to an element in the DOM. Selectors with fewer levels are not only easier to read but are also going to perform better. Always go for removing a level when deciding between using multiple child selectors or removing a level.
Since classes perform second-best, adding a class of its own name to each tag would have a significant increase in performance in all browsers, i.e. writing
<h1 class="h1"> so it can be selected with
.h1. However, this is a maintenance-nightmare. Do not do this unless rendering is a major bottleneck, which is very unlikely.
If a selector consisting of multiple tags contains a class somewhere, a new class could be added to the last element and used as the only selector. For example,
.main header h1 could be condensed to a single class, with the heading gaining this class in the markup (
<h1 class="main-header-heading">). This is going to have a negative effect on the markup in most cases and should only be used if safely applicable. Clean markup always trumps short selectors.
In accordance with the above rules, these selectors:
can safely be rewritten as:
Browser hacks relating to selectors (like
* html) should be avoided because they tend to require redefinition of both the problem properties and all other properties, leading to duplicate code.
If hacks cannot be avoided, property hacks like the star for IE7 and earlier (i.e.
*margin: 0;) and the underscore for IE6 and earlier (i.e.
_margin: 0;) should be used. These hacks should be used if IE-filters like
AlphaImageLoader are absolutely required and avoided in all other situations.
To avoid IE from slowing down a whole lot, pseudo-elements like
:hover should not be applied to non-link elements.
Preprocessors do not impact the performance of CSS when used correctly.
Writing bad stylesheets for preprocessing will result in bad stylesheets just the same as writing bad plain text stylesheets will. The primary concern lies in nesting selectors, which does increase the ability to easily find selectors because they can be organized to more closely mimic markup, but should never be nested deeper than absolutely necessary to be unambiguous.
Consider the following CSS as written in Sass:
While this makes it obvious that definition terms and definition descriptions are always nested in definition lists, this is the only way they can appear, so the nesting should be removed and the rules rewritten as:
Preprocessors do not result in a decrease in performance when respecting the aforementioned rules, assuming they are deployed as static assets and not processed client-side.
--style compressed flag set.
In addition to minification, stylesheets should be gzipped before delivery for a further decrease in filesize. Browsers have been able to deal with gzipped CSS-files for a long time. This is a server-side configuration (mod_gzip in Apache 1.3, mod_deflate in Apache 2.x).
The information in this post has mostly been rounded up from these sources, which may contain additional information for those that want to learn more about the topic: