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.
Key Takeaways
- Avoid global and element selectors to prevent styling conflicts and reduce the risk of bloat in your CSS. Instead, use class selectors to make your CSS more lightweight and reusable.
- Maintain low specificity in your selectors and avoid chaining class selectors. This helps create lightweight, reusable, and maintainable CSS, and reduces the risk of side effects.
- Use semantic class names that describe what the rule does or the type of content it affects. This helps your CSS endure changes in design requirements and makes it easier for you and your colleagues to understand the code.
- Avoid tying CSS too closely to markup structure. Using class selectors instead of child or descendant selectors allows you to change your markup without having to change your CSS, making your code more flexible and maintainable.
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:
Using <p class="message error">
, however, gives us the background of .message.error
and the border of .error
shown in Figure 2.2:
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 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.