DEV Community

Cover image for Lessons learned from over 10 years of writing CSS
Lukas Hermann
Lukas Hermann

Posted on • Originally published at lukashermann.dev

Lessons learned from over 10 years of writing CSS

Introduction

Through my years of using CSS, I have found some principles to stand the proof of time while others did cause me many headaches in the long run. I want to share with you the those that stood the test of time. If you have some good advice that is absent from this list please let me know, I am as much willing to learn as to share.

CSS is curious in that it is very easy to learn but near impossible to master.

Note: The advice in this article is concerned with the developer experience of CSS, and not with design, performance, or accessibility (though these are important). The following points will especially be of value to you if you need to maintain and extend your CSS codebase over longer periods.

Contents

  1. Keep specificity low
  2. #id selectors are evil
  3. Treat !important like raw eggs
  4. Avoid chaining (or nesting) of selectors
  5. Don't combine element and class selectors
  6. Organize your code by increasing specificity
  7. Use few properties per class
  8. Don't worry about sorting your properties
  9. Follow a naming convention like BEM
  10. Avoid using shorthands if possible
  11. Keep presentation (CSS), semantics (HTML) and behavior (JS) separate

1. Keep specificity low

It is really important to understand CSS specificity first, here is an excellent article. In essence, whenever you want to overwrite styles applied by one selector, you need to exceed or at least match its specificity.

🚫 Bad: specificity of first selector is too high

#main .nav li {
  color: white;
}

.nav li.is-active {
  color: red; /* dominated by #main */
}

✅ Good: using as few selectors as possible

.nav-item {
  color: white;
}

.nav-item.is-active {
  color: red;
}

To avoid the specificity wars, you want to avoid adding selectors that are not necessary. Instead of .nav ul li use only .nav li or better just .nav-item. Points 2. - 6. of this article are hands-on practices to help you to keep your specificity low.

2. #id selectors are evil

Id selectors have the highest specificity. This means whenever you use one, you are forced to use it again every time you want to modify the element's style. This might work well when you write your code in the first place but causes a headache whenever you want to extend it. I recommend generally avoiding #id selectors in your stylesheet.

🚫 Bad: forced to use the #footer selector repeatedly

#footer .box {
  background-color: #333;
}

#footer .box:hover {
  background-color: #444;
}

✅ Good: avoid using #id selectors in you stylesheet

.box {
  background-color: #333;
}

.box:hover {
  background-color: #444;
}

Further Reading: This article of Harry Roberts shows how you can use ids without increasing specificity

3. Treat !important like raw eggs

!important is the ace of spades of CSS, it is the ultimate trump card, and it should be treated as such. If you use it to escape the frustration of dealing with specificity which has gotten out of hand, then you are using it wrong. Use it sparingly, with selectors that have a very narrow scope and where you are certain that you want it to overpower all other styles.

🚫 Bad: using !important to deal with specificity frustration

#header .nav ul li a {
  border-bottom: 2px solid transparent;
}

.nav a:hover {
  border-bottom-color: black !important;
}

✅ Good: calculated use with narrow scope

.text-center {
  text-align: center !important;
}

Note: If you want to customize a CSS framework like Bootstrap that sometimes uses selectors with high specificity you may not have a choice

4. Avoid chaining (or nesting) of selectors

Especially with pre-processors like Sass, it is very easy to nest selectors which, when compiled to actual CSS, will be chained like this: .a .b .c .d { ... }

This might seem attractive because it helps you to write very specific CSS, but it also leads to bloated CSS. Let's say you have a .list class which is used inside .header and also inside .footer.

🚫 Bad: selectors are unnecessarily chained

.list {
  list-style: none;
  margin: 0;
}

.list .list-item {
  display: inline-block;
}

.header .list .list-item {
  margin-right: 1em;
}

.footer .list .list-item {
  font-weight: bold;
}

✅ Good: using as few selectors as possible

.list {
  list-style: none;
  margin: 0;
}

.list-item {
  display: inline-block;
}

.list--spaced .list-item {
  margin-right: 1em;
}

.list--bold .list-item {
  font-weight: bold;
}

There are two problems with this:

  1. To style the .list-item, the .list has to be carried along to match specificity.
  2. After a while you may be afraid to touch .list .list-item for fear that you break the .list-item in .header or .footer and have to undo it. You will probably end up writing any changes in .header .list .list-item or .footer .list .list-item directly, thus bloating your CSS and making it harder to keep a good overview.

The solution is not to use chaining/nesting with .header and .footer but to add modifier classes which can be used in connection with the .list class instead. Now you can write an element like this inside your .footer:

<ul class="list list--bold">
  <li class="list-item">&copy; Lukas Hermann</li>
</ul>

Not only does this reduce specificity (why use .list .list-item if you can just use .list-item), it only introduces higher specificity where we want it: when using a modifier.

A note concerning this specific example: In the case of lists it is probably easier to use .list li (and .list--bold li respectively) to save yourself some classes in your HTML markup, but I wanted to make an example which is easy to understand.

Note: This principle lies at the heart of naming conventions like BEM which will be addressed more specifically below.

5. Don't combine element and class selectors

When you start writing CSS it seems intuitive to keep your classes very specific. We all have been burned by the cascading nature of CSS and it can be upsetting if you make a change in your CSS intended for one small part of your site only to discover that you messed up the design of five other places.

With this background, it makes sense to write selectors like div.card a.link because we want to make sure that only anchor tags with .link inside a div with .card will receive this styling. But doing this is like fighting windmills, but CSS features are not our enemy. As a rule, don't combine element and class selectors, ever! If you want to modify the styling of an element in a particular place, use a modifier class instead.

🚫 Bad: mixing element and class selectors

a.link {
  color: blue;
}

div.card a.link {
  color: green;
}

✅ Good: using a modifier class instead

.link {
  color: blue;
}

.link--green {
  color: green;
}

The advantages of modifier classes:

  • The purpose of the modification is easy to understand
  • It's easy to change or remove
  • It's easy to find all the places in your code where this modifier is used by doing a simple search for link--green in your whole project

Note: A probable exception to this role might be a .btn component. If you want to make sure that an anchor tag a.btn looks identical to a button tag button.btn because these two elements behave a little different in browsers. But even then you may just as well add all the necessary styling to .btn.

6. Organize your code by increasing specificity

If two selectors have the same specificity, the latter will be used. Therefore, to play to the strengths of the CSS specificity feature, it makes sense to organize your CSS file by increasing specificity. Put your resets and element selectors first, your normal classes afterward, and finish with your utility classes like .text-center or .nowrap where you can then safely use !important.

🚫 Bad: unordered styles makes it easy to miss overwrites

.text-center {
  text-align: center !important;
}

.btn {
  background-color: green;
}

a {
  color: blue;
}

.btn {
  /* overwrites green */
  background-color: red;
}

✅ Good: ordering your styles roughly by specificity

/* Elements */
a {
  color: blue;
}

/* Components */
.btn {
  background-color: green;
}

.box {
  border-radius: 3px;
  box-shadow: 0px 3px 8px black;
}

/* Utilities */
.text-center {
  text-align: center !important;
}

The advantages of ordering styles roughly by specificity:

  • You will always overwrite less important styles with more important ones
  • You have a rough guideline for where your things go inside your CSS file

Note: This principle was introduced as ITCSS by Harry Robers. It is a really useful CSS methodology to handle large-scale products. Here is the article introducing ITCSS. Harry even created a course on Skillshare just recently.

7. Use few properties per class

As a general rule, the more properties a class has, the less likely you are going to reuse it. Simply because of the fear that some of the many properties will not fit your other use case and you have to undo them, thus you might even end up writing another class with very similar properties.

A good way to know if your classes are too bloated:

  1. Simply observing yourself how many resets (like margin: 0;) you are writing to undo other classes properties
  2. If you feel uncomfortable about reusing classes or if feel bad about adding another property to a class, it's a sign this class is too bloated

Classes become bloated because many different concerns (aesthetics, spacing, typography) are packed together. A great way to improve this, even without dropping any properties, is to simply break it up into its different concerns.

🚫 Bad: bloated, properties of different concerns are mixed

.card {
  border-radius: 3px;
  box-shadow: 0 5px 10px black;
  padding: 2rem 1rem;
  font-size: 14px;
  line-height: 1.3;
  margin-bottom: 2rem;
}

Note: The bloated example is not overly bloated yet, but I think it brings the point across.

✅ Good: fewer properties keeps classes reusable

.card {
  border-radius: 3px,
  box-shadow: 0 5px 10px black;
  padding: 2rem 1rem;
}

.spacer {
  margin-bottom: 2rem;
}

.text-small {
  font-size: 14px;
  line-height: 1.3;
}

In the good example, the .card class only contains the properties which specifically define its aesthetics. The aspects of spacing and typography are outsourced into their own classes. Having broken up the class like this you can easily see how to reuse each of those classes and even extend them and add more classes like .spacer-large or .text-tiny which will be very useful.

Yes, this means that you have to add more classes in your HTML like so <div class="card spacer text-small">, and that's okay. It is much more comfortable, especially in the long run, to use such classes.

Some classes need a lot of properties, for example, a .button class has to unset many user agent (browser) styles for both, the <button> and the <a> tag. Therefore this rule should not be followed religiously for every case.

Tipp: Most CSS frameworks come equipped with a good set of reusable utility classes, watch out for them and use them!

8. Don't worry about sorting your properties

This one is a bit opinionated. Brad Frost did a survey on twitter how people order their CSS properties:

Most people answered that they are grouping properties by some common traits like positioning, display, or color. Some may be OCD about it and sort it alphabetically. Personally, I have tried these different methods and haven't found any of them particularly useful in the long run. I basically always do scrambled egg ... or how Brad Frost put it, "train wreck".

If your classes have many properties some sorting rules usually become imperative to maintain an overview. But if you keep your classes slim, like suggested in point 7, it is really not necessary. Simply abide by some simple rules (like layout first, aesthetics last), and don't worry too much about the order otherwise.

9. Follow a naming convention like BEM

Some of the code examples in this article are using the BEM naming convention. I found it to be very helpful and recommend it wholeheartedly. It's easy to learn! Quickly head to this small introduction with good code examples of the BEM naming convention to get the idea.

Here are two points I struggled with at the beginning with BEM.

Don't nest elements

If you have a title within a header within a card, only "nest" maximum one level deep like so:

🚫 Nesting BEM selector produces ugly classes

.card {}
.card__header {}
.card__header__title {}

✅ Nesting BEM only one level deep is better

.card {}
.card__header {}
.card__title {}

It's best to use modifiers only on root elements

Modifiers on children can be used, but they make it hard to remember which child has which modifier. If possible, apply the modifier to the root element, this will keep your HTML markup and your CSS cleaner and therefore easier to see which modifiers are used or available.

🚫 Using BEM modifiers on children get's messy

.card__header--large { padding: 2em; }
.card__content--large { padding: 0 2em; }
.card__footer--large { padding: 2em; }

✅ Restricting BEM modifiers to root elements is cleaner

.card--large .card__header { padding: 2em; }
.card--large .card__content { padding: 0 2em; }
.card--large .card__footer { padding: 2em; }

10. Avoid using shorthands if possible

When defining a background color it is very common to just use shorthands like background: gray;. It is important to know, however, that this shorthand reset's all other background properties, like background-position and so on, to initial. Similar case with other shorthands like border and margin.

This causes problems only in the rarest of cases, but if it does, it will be very hard to debug. It is therefore best practice to only change as little as necessary. Be safe and use background-color etc., the prevented headache is worth the extra characters.

🚫 Using shorthands can have unintended side effects

.card {
  background: lightgray;
  border: 0;
  margin: 0 2rem;
}

✅ Using verbose properties helps prevent side effects

.card {
  background-color: lightgray;
  border-width: 0;
  margin-left: 2rem;
  margin-right: 2rem;
}

Further reading: Harry Roberts makes a great case for this in CSS Shorthand Syntax Considered an Anti-Pattern

11. Keep presentation (CSS), semantics (HTML) and behavior (JS) separate

I often see styling tied to ids or elements (like #nav or .nav ul li), or javascript behavior tied to a class (like document.getElementsByClassName(".nav")). However, if you do this you rob yourself of the flexibility to keep presentation (CSS), semantics (HTML), and behavior (JS) separate. You want to be able to style something as a button, regardless if it is an <a>, <button> or <div> (note that the latter is not good for accessibility). Or if you create a navbar, you want to be able to use <li>-tags or <div>-tags depending on the necessary semantics, without losing your styling or have to change the CSS.

To achieve this use ids for javascript (as originally intended), or better, data-attributes like so: document.querySelectorAll('[data-nav]'); and don't mingle the button element selector with your styles. This gives you the freedom to change either semantics, presentation, or behavior independently without having to worry about the other two.

🚫 Bad: mixing presentation and behavior

<button class="btn btn-login">Login</button>

<script>
  const el = document.getElementsByClassName('.btn-login');
  el.addEventListener('click', function (event) { ... }, false);
</script>

<style>
  .btn-login {
    color: white;
    background-color: green;
  }
</style>

✅ Good: presentation and behavior are kept separate

<button class="btn btn-green" data-login>Login</button>

<script>
  const el = document.querySelectorAll('[data-login]');
  el.addEventListener('click', function (event) { ... }, false);
</script>

<style>
  .btn-green {
    color: white;
    background-color: green;
  }
</style>

Exception: There are reasons to use element selectors for styling. For example, if you intentionally set global presets, like a reset or change the default link color. But be careful not to set presets that you find yourself overwriting all the time.

Top comments (1)

Collapse
 
anilsansak profile image
Yaşar Anıl Sansak

Great article and amazing tips! Learned a lot of things. Thank you.