As we know today, the CSS language is an essential part of the web. It gives us the ability to draw the way elements are presented on a screen, web page or other media.

It is simple, powerful, and declarative. We can easily implement complex things like dark/light patterns. However, there are many misconceptions and misuses of it. These can turn CSS markup into complex unreadable and unscalable code.

How can we prevent this from happening? By following best practices and avoiding the most common mistakes. In this article, we will summarize the 5 most common mistakes and how to avoid them.

1. Not designing in advance

Doing something immediately without thinking about it may get the job done faster, and it gives us a sense of speed and accomplishment. But, in the long run, this will have the opposite effect.

Before writing code, it is important to think it through. What approach will we take to designing the component? Do we want to build our components in an atomic way? Do we want to create a composable utility system? Do we want a UI library that is already built in? Do we want our CSS to be globally scoped or per-component scoped?

Having a clear goal will help us choose the best tool for the job. This will save us from redundancy and DRY violations . There are many valid ways to design an application. The most common ineffective one is improvisation.

Our code must be predictable, easy to extend and maintain.

Look at an example.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
/* ❌ 到处添加离散值 */
.card {
  color: #edb361;
  background-color: #274530;
  padding: 1rem;
}

/* ✅ 定义基于主题的属性 */
:root {
  --primary-bg-color: #274530;
  --accent-text-color: #edb361;
  --spacing-unit: 0.5;
}

.card {
  color: var(--accent-text-color);
  background-color: var(--primary-bg-color);
  padding: calc(var(--spacing-unit) * 2rem);
}

In the example above, we can see how everything becomes readable and clear when using CSS variables for the theme design. The first .card definition looks completely random and this component is not easily extendable.

2. CSS Code Smells

Code Smell is a hint that there is a bug somewhere in the code, and developers can use this smell to track down the problem in the code.

Code smells are not bugs, and they do not prevent the system from working properly. They are just bad practices that can make our code harder to read and maintain.

Here is a list of some of the most common ones and how to overcome them.

:: notation

It is common to use the :: notation in pseudo elements and pseudo classes. It is part of the old CSS specification and browsers continue to support it as a fallback. However, we should use :: in pseudo elements such as ::before, ::after, ::frist-line… We should use :: in pseudo-classes such as :link, :visited, :first-child…

Using String Concatenation Classes

It is very popular to use the Sass preprocessor to help with our CSS codebase. Sometimes when trying to DRY, we create classes by concatenating & operators.

1
2
3
4
5
6
7
8
.card {
  border: 0.5 solid rem #fff;
  
  /* ❌ failed attempt to be dry */
  &-selected {
    border-color: #000;
  }
}

There seems to be no problem until developers try to search for the .card-selected class in the code base. Developers will have a hard time finding this class.

Incorrect use of abbreviations

CSS abbreviations are great and allow us to avoid overly lengthy code. However, sometimes we don’t use them intentionally. Most of the time, background abbreviations are used by accident.

1
2
3
4
5
6
7
8
9
/* ❌ 由于我们只是在设置一个属性,所以不需要使用简写。*/
.foo {
  background: #274530;
}

/* ✅ 使用正确的CSS属性 */
.foo {
  background-color: #274530;
}

Incorrect use of !important

The !important rule is used to override specificity rules. Its use is mainly focused on overriding a style that cannot be overridden in any other way.

It is usually used in scenarios where more specific selectors can do the job.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<div class="inner">
  <p>This text is in the inner div.</p>
</div>


<style>
  .inner {
    color: blue;
  }
  
  /* ❌ 重写 color */
  .inner {
    color: orange !important;
  }
</style>


<style>
  .inner {
    color: blue;
  }
  
  /* ✅ 使用一个更具体的选择器规则,该规则将优先于更一般的规则。 */
  .inner p {
    color: orange;
  }
</style>

Forced use of attribute values

It is quite common for a magic number to appear in the CSS codebase. They create quite a bit of confusion. Sometimes we may find long numbers in the code because the developer is trying to override a property he is not sure about.

1
2
3
4
5
6
7
8
9
/* ❌ Brute 强制使这个元素位于z轴的最前面 */
.modal-confirm-dialog {
  z-index: 9999999;
}

/* ✅ 提前计划并定义所有可能的用例 */
.modal-confirm-dialog {
  z-index: var(--z-index-modal-type);
}

3. No scoping of CSS class names

Due to the nature of the CSS language, it is easy for elements to be inadvertently stereotyped by a bad class name. This problem is very frequent, so there are quite a few solutions to this problem.

In my opinion, the two best ones are.

  • using naming conventions
  • CSS Modules

Naming Conventions

The most popular naming convention is BEM 101. It stands for Block, Element, and Modifier methods.

1
2
3
4
5
[block]__[element]--[modifier]
/* Example */
.menu__link--blue {
  ...
}

Its purpose is to create unique names by allowing developers to understand the relationship between HTML and CSS.

CSS Modules

My biggest concern with the BEM method is that it is time consuming and relies on the developer to implement it. CSS modules happen on the preprocessor side, which makes it error free. It generates random prefixes/names for our CSS module class names.

4. Using px units

Pixels are used quite often because it seems easy and intuitive to use at first. The truth is quite the opposite. For a long time now, pixels are no longer based on hardware. They are simply based on an optical reference unit.

The px is an absolute unit. What does this mean? That we can’t scale properly to meet more of them.

What should we use instead? Relative units are the way to go. We can rely on these to better express our dynamic layout. For example, we can use ch to express the width of a div based on the number of characters.

1
2
3
4
.article-column {
  /* ✅  我们的元素将最多容纳20个继承的字体大小的字符。 */
  max-width: 20ch;
}

Typically, the most common replacements for px are rem and em. They represent the relative size of a font in a box-to-text relative fashion.

  • rem indicates the size relative to the root font-size.
  • em indicates the size relative to the element size.

By using rem, we will be able to express the layout according to the user’s preferred font size.

css

In the screenshot above, we can see how the rem unit-based layout can scale and adapt to different default font sizes.

5. Ignore browser support

When starting to develop a website, it is crucial to define our target audience. It is common to skip this step and go straight to coding.

Why is it crucial? It helps us to understand on which devices our application will be used. After that, we can define which browsers and which versions we will support.

We can still work towards accepting later features like subgrid, as long as we can provide the proper fallbacks. It is always a good idea to define an incremental feature experience. As a feature gains more support, we can gradually discard its fallback.

Tools like caniuse.com or browserslist.dev are helpful in this regard. The autoprefixer feature that comes with tools like postcss will help us get broader support for CSS.

Summary

We’ve seen how to improve our CSS code. By following some simple guidelines, we can achieve a declarative, reusable and readable codebase. We should put as much effort into CSS as we do into Javascript.