Golden Guidelines for Writing Clean CSS

Share this article

Golden Guidelines for Writing Clean CSS

The following is an extract from our book, CSS Master, written by Tiffany Brown. Copies are sold in stores worldwide, or you can buy it in ebook form here.

Golden Guidelines for Writing Clean CSS

As mentioned, there are some rules for writing clean CSS that you should try your best to avoid breaking. They’ll help you write CSS that is lightweight and reusable:

  • Avoid global and element selectors
  • Omit overly specific selectors
  • Use semantic class names
  • Don’t tie CSS too closely to markup structure

Let’s look at these one by one.

Avoid Global Selectors

Global selectors include the universal selector (*), element selectors such as p, button, and h1, and attribute selectors such as [type=checkbox]. Style declarations applied to these selectors will be applied to every such element across the site. Here’s an example:

button {
  background: #FFC107;
  border: 1px outset #FF9800;
  display: block;
  font: bold 16px / 1.5 sans-serif;
  margin: 1rem auto;
  width: 50%;
  padding: .5rem;
}

This seems innocuous enough. But what if we want to create a button that’s styled differently? Let’s style a .close button that will be used to close dialog modules:

<section class="dialog"> 
  <button type="button" class="close">Close</button> </section>
Note: Why not use dialog?

We’re using section here instead of the dialog element because support for dialog is limited to Blink-based browsers such as Chrome/Chromium, Opera, and Yandex.

Now we need to write CSS to override every line that we don’t want to inherit from the button rule set:

.close {
  background: #e00;
  border: 2px solid #fff;
  color: #fff;
  display: inline-block;
  margin: 0;
  font-size: 12px;
  font-weight: normal;
  line-height: 1;
  padding: 5px;
  border-radius: 100px;
  width: auto;        
}

We’d still need many of these declarations to override browser defaults. But what if we scope our button styles to a .default class instead? We can then drop the display, font-weight, line-height, margin, padding, and width declarations from our .close rule set. That’s a 23% reduction in size:

.default {
  background: #FFC107;
  border: 1px outset #FF9800;
  display: block;
  font: bold 16px / 1.5 sans-serif;
  margin: 1rem auto;
  width: 50%;
  padding: .5rem;
}

.close {
  background: #e00;
  border: 2px solid #fff;
  color: #fff;
  font-size: 12px;
  padding: 5px;
  border-radius: 100px;
}

Just as importantly, avoiding global selectors reduces the risk of styling conflicts. A developer working on one module or document won’t inadvertently add a rule that creates a side effect in another module or document.

Global styles and selectors are perfectly okay for resetting and normalizing default browser styles. In most other cases, however, they invite bloat.

Avoid Overly Specific Selectors

Maintaining low specificity in your selectors is one of the keys to creating lightweight, reusable, and maintainable CSS. As you may recall on specificity, a type selector has the specificity 0,0,1. Class selectors, on the other hand, have a specificity of 0,1,0:

/* Specificity of 0,0,1 */
p {
  color: #222;
  font-size: 12px;
}

/* Specificity of 0,1,0 */
.error {
  color: #a00;
}

When you add a class name to an element, the rules for that selector take precedence over more generic-type selector rules. There’s no need to further qualify a class selector by combining it with a type selector. Doing so increases the specificity of that selector and increases the overall file size.

Put differently, using p.error is unnecessarily specific because .error achieves the same goal. Another advantage is that .error can be reused with other elements. A p.error selector limits the .error class to p elements.

Don’t Chain Classes

Also avoid chaining class selectors. Selectors such as .message.warning have a specificity of 0,2,0. Higher specificity means they’re hard to override, plus chaining often causes side effects. Here’s an example:

message {
  background: #eee;
  border: 2px solid #333;
  border-radius: 1em;
  padding: 1em;
}
.message.error {
  background: #f30;
  color: #fff;
}
.error {
  background: #ff0;
  border-color: #fc0;
}

Using <p class="message"> with this CSS gives us a nice gray box with a dark gray border, as seen in Figure 2.1:

Figure 2.1. The visual effect of our .message selector

Using <p class="message error">, however, gives us the background of .message.error and the border of .error shown in Figure 2.2:

Figure 2.2. The visual result of using .message.error as a selector

The only way to override a chained class selector would be to use an even more specific selector. To be rid of the yellow border, we’d need to add a class name or type selector to the chain: .message.warning.exception or div.message.warning. It’s more expedient to create a new class instead. If you do find yourself chaining selectors, go back to the drawing board. Either the design has inconsistencies, or you’re chaining prematurely in an attempt to prevent problems that you don’t have. Fix those problems. The maintenance headaches you’ll prevent and the reusability you’ll gain are worth it.

Avoid Using id Selectors

Because you can only have one element per id per document, rule sets that use id selectors are hard to repurpose. Doing so typically involves using a list of id selectors; for example, #sidebar-features and #sidebar-sports.

Identifiers also have a high degree of specificity, so we’ll need longer selectors to override declarations. In the CSS that follows, we need to use #sidebar.sports and #sidebar.local to override the background color of #sidebar:

#sidebar {
  float: right;
  width: 25%;
  background: #eee;
}
#sidebar.sports  {
  background: #d5e3ff;
}
#sidebar.local {
  background: #ffcccc;
}

Switching to a class selector, such as .sidebar, lets us simplify our selector chain:

sidebar {
  float: right;
  width: 25%;
  background: #eee;
}
.sports  {
  background: #d5e3ff;
}
.local {
  background: #ffcccc;
}

As well as saving us a few bytes, our .sports, and .local rule sets can now be added to other elements.

Using an attribute selector such as [id=sidebar] lets us get around the higher specificity of an identifier. Though it lacks the reusability of a class selector, the low specificity means that we can avoid chaining selectors.

Note: When the High Specificity of id Selectors is Useful

In some circumstances, you might want the higher specificity of an id selector. For example, a network of media sites might wish to use the same navigation bar across all of its web properties. This component must be consistent across sites in the network, and should be hard to restyle. Using an id selector reduces the chances of those styles being accidentally overridden.

Finally, let’s talk about selectors such as #main article.sports table#stats tr:nth-child(even) td:last-child. Not only is it absurdly long, but with a specificity of 2,3,4, it’s also not reusable. How many possible instances of this selector can there be in your markup? Let’s make this better. We can immediately trim our selector to #stats tr:nth-child(even) td:last-child. It’s specific enough to do the job. Yet the far better approach—for both reusability and to minimize the number of bytes—is to use a class name instead.

Note: A Symptom of Preprocessor Nesting

Overly specific selectors are often the result of too much preprocessor nesting.

Use Semantic Class Names

When we use the word semantic, we mean meaningful. Class names should describe what the rule does or the type of content it affects. We also want names that will endure changes in the design requirements. Naming is harder than it looks.

Here are examples of what not to do: .red-text, .blue-button, .border-4px, .margin10px. What’s wrong with these? They are too tightly coupled to the existing design choices. Using class="red-text" to mark up an error message does work. But what happens if the design changes and error messages become black text inside orange boxes? Now your class name is inaccurate, making it tougher for you and your colleagues to understand what’s happening in the code.

A better choice in this case is to use a class name such as .alert, .error, or .message-error. These names indicate how the class should be used and the kind of content (error messages) that they affect. For class names that define page layout, add a prefix such as layout-, grid-, col-, or simply l- to indicate at a glance what it is they do. The section on BEM methodology later on describes a process for this.

Avoid Tying CSS Closely to Markup

You’ve probably used child or descendant selectors in your code. Child selectors follow the pattern E > F where F is an element, and E is its immediate parent. For example, article > h1 affects the h1 element in <article><h1>Advanced CSS</h1></article>, but not the h1 element in <article><section><h1>Advanced CSS</h1></section></article>. A descendant selector, on the other hand, follows the pattern E F where F is an element, and E is an ancestor. To use our previous example, article h1 selects the h1 element in both cases.

Neither child nor descendant selectors are inherently bad. In fact, they work well to limit the scope of CSS rules. But they’re far from ideal, however, because markup occasionally changes.

Raise your hand if you’ve ever experienced the following. You’ve developed some templates for a client and your CSS uses child and descendant selectors in several places. Most of those children and descendants are also element selectors, so selectors such as .promo > h2 and .media h3 are all over your code. Your client also hired an SEO consultant, who reviewed your markup and suggested you change your h2 and h3 elements to h1 and h2 elements. The problem is that we also have to change our CSS.

Once again, class selectors reveal their advantage. Using .promo > .headline or .media .title (or more simply .promo-headline and .media-title) lets us change our markup without having to change our CSS.

Of course, this rule assumes that you have access to and control over the markup. This may not be true if you’re dealing with a legacy CMS. It’s appropriate and necessary to use child, descendant, or pseudo-class selectors in such cases.

Note: More Architecturally Sound CSS Rules

Philip Walton discusses these and other these rules in his article “CSS Architecture.” I also recommend Harry Roberts’ site CSS Guidelines and Nicolas Gallagher’s post About HTML Semantics and Front-end Architecture for more thoughts on CSS architecture.

We’ll now look at two methodologies for CSS architecture. Both methods were created to improve the development process for large sites and large teams; however, they work just as well for teams of one.

Frequently Asked Questions on Writing Clean CSS

What are the key principles for writing clean CSS?

Writing clean CSS involves adhering to certain principles. Firstly, it’s important to keep your code DRY (Don’t Repeat Yourself). This means avoiding unnecessary repetition in your CSS code. Secondly, use comments to explain your code. This makes it easier for others (or yourself in the future) to understand what your code does. Thirdly, use meaningful names for your classes and IDs. This makes your code more readable and understandable. Lastly, organize your code in a logical manner. This could be by grouping similar styles together or by following a specific order.

How can I optimize my CSS for better performance?

Optimizing your CSS can significantly improve your website’s loading speed. One way to do this is by minifying your CSS, which involves removing unnecessary characters from your code. You can also use shorthand properties to reduce the size of your CSS. Additionally, avoid using @import as it can slow down your page load time. Instead, use multiple link tags or combine your CSS files into one.

What are some common mistakes to avoid when writing CSS?

Some common mistakes include overusing !important, which can make your code harder to debug and maintain. Another mistake is using too many fonts or colors, which can make your website look cluttered and unprofessional. Also, avoid using inline styles as they can make your code messy and difficult to maintain. Instead, use external stylesheets or embedded styles.

How can I make my CSS code more readable?

Making your CSS code more readable involves following certain best practices. For instance, use indentation to show the hierarchy of your code. Also, use comments to explain complex parts of your code. Additionally, use meaningful names for your classes and IDs. Lastly, organize your code in a logical manner, such as grouping similar styles together or following a specific order.

What tools can I use to clean my CSS?

There are several tools available that can help you clean your CSS. These include CSS Lint, which can identify potential issues in your code, and CSS Compressor, which can minify your CSS to improve performance. Additionally, tools like CSS Beautifier can reformat your CSS to make it more readable.

How can I ensure consistency in my CSS code?

Ensuring consistency in your CSS code involves following a set of coding conventions. This could include things like always using lowercase for class and ID names, always using a space before the opening brace, and always using a semi-colon after each declaration. Additionally, using a CSS preprocessor like Sass or Less can help enforce consistency in your code.

What is the role of CSS in responsive design?

CSS plays a crucial role in responsive design. It allows you to create different layouts for different screen sizes using media queries. Additionally, CSS can be used to create flexible layouts using percentages instead of fixed units, and to resize images so they fit within their container.

How can I avoid CSS conflicts?

Avoiding CSS conflicts involves following certain best practices. For instance, use specific selectors to target elements, rather than relying on inheritance. Also, avoid using !important as it can override other styles. Additionally, use a CSS reset to ensure consistency across different browsers.

How can I test my CSS code?

Testing your CSS code involves checking it in different browsers and on different devices to ensure it works as expected. Tools like BrowserStack can help with this. Additionally, use a CSS validator to check for errors in your code.

How can I keep up with the latest CSS trends and techniques?

Keeping up with the latest CSS trends and techniques involves regularly reading blogs, tutorials, and documentation. Websites like CSS-Tricks, Smashing Magazine, and MDN Web Docs are great resources. Additionally, attending web development conferences or meetups can also be beneficial.

Tiffany BrownTiffany Brown
View Author

Tiffany B. Brown is a freelance web developer and technical writer based in Los Angeles. Brown offers web development and consulting services to larger agencies and small businesses. A former member of the Opera Software developer relations team, Brown is also co-author of SitePoint's JumpStart HTML5 book. She sporadically writes about web development technology on her blog. You can follow her on Twitter at @webinista.

AdvancedCSSbookCSSexcerpthtml
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week