Skip to content

Common CSS Mistakes (And How To Fix Them)

Published on

These are some common causes of design, scalability, performance and maintainability issues I find when refactoring CSS for clients:

Using units for setting line heights

Using units when setting your line heights will make child elements inherit the computed value from their parent. The lack of relativity will force you to keep adding line-heights to override previous values:

h1 {
  font-size: 48px;
  line-height: 72px;
}
.post-title {
  font-size: 36px;
  line-height: 54px;
}
.page-title {
  font-size: 72px;
  line-height: 108px;
}

With unitless line heights, child elements will inherit the raw number, and will be able to compute their own line height based on font size.

h1 {
  font-size: 48px;
  line-height: 1.5;
}
.post-title {
  font-size: 36px;
}
.page-title {
  font-size: 72px;
}

Of course, if you seriously care about achieving a “perfect” vertical rhythm, then you will most likely need to set your line heights individually. It’s up to you to decide if you’d rather sacrifice maintainability and simplicity for the sake of perfection.

Pixel units everywhere

You can always save yourself some time and set your font sizes, margins and paddings using rem and em units. Pixels are simple and consistent, which sometimes is desirable, but there are a few neat tricks you can achieve with relative units:

html {
  font-size: 100%;
}

h1 { 
  font-size: 3rem;
  margin: 1rem 0;
}

/* let's proportionally scale the site for massive screen sizes */
@media (min-width: 2000px) {
  html {
    font-size: 100vw;
  }
}

You can also combine rems and ems for better results. For example:

.button {
  font-size: 1.125rem;
  padding: .5em 1em;
}

/* ems will scale with font size, so you don’t have to declare paddings again */
.button--large {
  font-size: 1.5rem;
}

Device breakpoints

"Apple aren’t the only people who make handheld devices with browsers on them. No really."
Heydon Pickering
"Start with the small screen first, then expand until it looks like shit. Time for a breakpoint!"
Stephen Hay

Most likely, you’ll find different components will break at different widths. Use major (global) and minor (component-specific) breakpoints.

Avoiding !important

There are legit use cases for !important, like forcing immutability in utility classes. Do avoid using it as a quick way to deal with specificity issues.

No comments

Commenting your CSS will make life easier for anyone inheriting your code. As a rule of thumb, you want to comment on anything that isn’t obvious from your code alone.

Overdoing comments

Go overboard with comments and people will forget to update them. Unreliable comments are worse than no comments at all.

/*------------------------------------------------------------------
[Master Stylesheet]
Project:  Smashing Magazine
Version:  1.1
Last change:  05/02/08 [fixed Float bug, vf]
Assigned to:  Vitaly Friedman (vf), Sven Lennartz (sl)
Primary use:  Magazine 
-------------------------------------------------------------------*/
@import "reset.css";
@import "layout.css";
@import "colors.css";
@import "typography.css";
@import "flash.css";
/* @import "debugging.css"; */

To make your code more maintainable, avoid redundancy. Some things that shouldn’t be in your comments:

  • Things that are obvious from the code alone.
  • Unit conversions (i.e: font-size: 1.5rem; // 24px).
  • Changes and revisions. We have version control for that!

Hard-coded values

Following the DRY principle, every piece of knowledge must have a single, unambiguous and authoritative representation within your stylesheet.

Take this example:

h1 {
  margin: 30px 0;
}

.hero {
  margin-bottom: 30px;
}

.marg-top {
  margin-top: 30px;
}

What if we decide that we want to do 40px instead of 30px? You’ll have to remember to change every one of these values manually. Usually these rules will be spread across multiple files, which makes it even worse.

You can either use a preprocessor or CSS custom variables, and you’ll only have to change your value once:

$base-font-size: 16px;
$base-line-height: 1.5;
$spacing-unit: $base-font-size * $base-line-height;

h1 {
  margin: rem($spacing-unit);
}

.hero {
  margin-bottom: rem($spacing-unit);
}

.marg-top {
  margin-top: rem($spacing-unit);
}

Working against the cascade

The cascade, inheritance and specificity make CSS a huge chronology where source order matters a LOT. Ignore this fact and headaches will follow.

These are all equally bad ideas:

  • Organizing CSS to mimick the design (i.e: header at the top of the stylesheet, footer at the end).
  • Organizing CSS in random, multiple files without considering specificity. Multiple files are not a frigging panacea.
  • Adding new CSS at the bottom of the stylesheet.

The principles behind ITCSS make a lot of sense here: Start with generic, low-specificity, far-reaching styles (i.e: bare html elements) and progress to more explicit, specific and smaller ones (i.e: utility classes carrying !important rules).

ID’s inside selectors

ID’s have higher specificity than classes and will do more harm than good in the long run.

Specificity of ID's

Use ID’s in HTML. Don’t use ID’s in CSS. If you have to, lower their specificity to class level:

[id="YourID"] {
  // your rules
}

Abusing extends

Extends are one of those things that people tend to abuse… to the point of creating entire methodologies around them. Ugh.

While extends make sense in some (limited) cases, I’m still to find a case where I couldn’t use a mixin with similar results and less potential issues.

Mixins will help you keep your code DRY while avoiding most of the nightmares that come with extends, like 125 lines CSS selectors.

Bad selector intent

Avoid making selectors more specific or qualified than strictly necessary. I’m borrowing this example from Philip Walton:

/* Grenade */
#main-nav ul li ul { }

/* Sniper Rifle */ 
.subnav { }

This is not about selector efficiency. It’s about reducing unnecessary specificity and increasing reusability.

Always choose sniper rifles over grenades. I’m still talking about code.

Generic classnames

Generic classnames are more likely to be used for different purposes throughout large projects, producing unreliable code:

.card .title {
  color: #000;
}

.post .title {
  color: #666;
  font-size: 1.5rem;
}

Now what happens if you place a card component within a post?

<article class="post">
  <h1 class="title">Post title</h1>
  <div class="card">
    <h3 class="title">Card title</h3>
  </div>
</article>

In this case, your card title will inherit an unwanted font-size rule from the post title. An easy fix for this particular case would be to use the child selector:

.post > .title {
  color: #666;
  font-size: 1.5rem;
}

.card > .title {
  color: #000;
}

However, a more sustainable way of fixing it, is to use BEM-like notation. This paints a clearer picture of the relationship between different elements:

.post__title {
  color: #666;
  font-size: 1.5rem;
}

.card__title {
  color: #000;
}

And your HTML would look like this:

<article class="post">
  <h1 class="post__title">Post Title</h1>
  <div class="card">
    <h3 class="card__title">Card Title</h3>
  </div>
</article>

If you’re interested, I suggest you take a look at BEMIT, which takes BEM even further by adding namespaces and responsive suffixes into the mix.

Monoliths

Monolithic rulesets make it difficult to introduce changes and variations to your components. You will often find yourself writing more CSS to achieve less styling (always a bad symptom):

.button {
  display: inline-block;
  padding: .5em 1em;
  color: #666;
  background: #eee;
  font-size: 1rem;
  border: 1px solid #ddd;
  text-shadow: 1px 1px 1px #000;
}

.button.red {
  color: white;
  background: red;
  border: none;
}

.button.big {
  font-size: 2.5rem;
  padding: 2em 3em;
}

.button.no-shadow {
  text-shadow: none;
}

Breaking down your buttons into smaller classes will help you re-use classes and introduce variations with ease:

.button {
  display: inline-block;
}

.button--gray {
  background: #eee;
  color: #666;
  border: 1px solid #ddd;
}

.button--red {
  background: red
  color: white;
}

.button--small {
  padding: .5em 1em;
  font-size: 1rem;
}

.button--medium {
  padding: 1em 2em;
  font-size: 1.5rem;
}

.button--large {
  padding: 2em 3em;
  font-size: 2.5rem;
}

.button--shadow {
  text-shadow: 1px 1px 1px #000;
}

Join my newsletter and get articles like this delivered straight into your inbox.