CSS is like the dentist for many engineers, an unpleasant experience you can’t avoid. They believe that CSS inevitably devolves into a mess of spaghetti as the codebase grows.

It doesn’t have to be this way.

CSS is a programming language, and like any language it can be elegant and pleasant to use.

Reading Clean Code by Robert Martin changed the way I code. With simple, concrete examples he showed what makes code fragile and unclear (and how to fix it). He called these rules “code smells” and they were immensely useful in applying abstract design principles to real-world code.

My goal here is to show how you can write clean CSS by recognizing code smells.

The CSS Stack

Before we dive into these code smells, I unapologetically insert you:

  1. Use Bootstrap. The framework not only saves a lot of time, but it is easy to extend and establishes a very clean foundation for all future code.
  2. Use LESS or SASS. Both are languages that generate CSS for you and can be used easily with Bootstrap. I’ll use LESS in this article, but they’re largely equivalent.

The Single Responsibility Principle

All high-level coding principles really boil down to this: make your code modular. “Modular” means your code is broken into clearly-defined, independent units.

The easiest test for modularity is the Single Responsibility Principle (SRP), which states that your code should do one thing. As Martin states, it doesn’t matter if your code is a library, class, function or even a single variable, it “should have only one reason to change.”

Most of these code smells are signs that we are violating the SRP, and the code should be refactored.

Modular CSS = Components

Since CSS currently lacks a concept of modules, you have to create them by designing components. A “component” is a clearly defined visual element like a dropdown menu or a paragraph of text.

The benefits are huge:

  1. Components cleanly encapsulate complex UI elements.
  2. Components simplify testing because they can be tested in isolation.
  3. Your application will have consistent design.
  4. Development is a lot faster. When I was at Box we cut the time to build a feature in half by moving to CSS components because we didn’t spend the time pixel-pushing and fixing bugs.
  5. You only need one CSS expert to build and publish components which everyone can use (like any other library).

Build Your Design Process around Components

The biggest challenge to adopting component-driven CSS is that your designers and PMs will also have to think in terms of components.

The good thing is that design theory is on your side. Consistency is essential to great design, and writers like Don Norman argue that simple design elements are critical to helping your users create mental model for your product.

This is a hard sell to graphic designers that love introducing artistic flourishes into every mockup, but it should really appeal to designers that care about a broader sense of user experience and design. By designing reusable components they’ll spend less time reinventing basic functionality and more time focusing on customer needs, interaction design, and metrics. Engineers will build their new designs far more quickly and with far fewer quirks.

CSS Code Smells

High-level design principles provide a great foundation, but it can be difficult to apply to them to messy world of code. This is why “code smells” are so useful: when you see these examples in code (or in code review) you can easily tell that they are wrong and why.

CSS rules that do more than one thing

.comment-box {
  /* here we are styling what the comment-box looks like */
  border: solid 1px black;
  text-color: gray;

  /* but here were are rigidly defining where it goes */
  position: static;
  left: 0;
  top: 200px;
}

What’s going on

This code works fine the first time you use a comment-box, but now you can’t re-use it in another spot without overriding the CSS.

This is a direct violation of the SRP: the class has tightly coupled the visuals of

Solution

This is a case where you want two rules: one to style a comment-box and another to position it.

CSS rules without a clear responsibility

.clearfix {
  clear: both;
}

What’s going on

What is clearfix responsible for? This is really a technical detail of CSS, not a responsibility.

Solution

Implementation details like a clearfix should be encapsulated by a broader component like a grid layout or a navbar that has a clear responsibility.

Co-dependent CSS rules

Here is an example taken directly from Bootstrap’s button styling:

.btn {
  /* define generic button style */
  padding: 2px 10px;
  border-radius: 4px;
  ...
}
.btn-default {
  /* define one variant of the button */
  color: black;
  background-color: white;
  border: 1px solid #CCC;
  ...
}

What’s going on

The two classes violate the SRP because neither one works correctly without the other (they’re “co-dependent”).

Solution

Using LESS there is a much cleaner way to write this as:

.btn-default {
  .abstract-btn;  /* inherit styling that was in the old .btn class */
  color: black;
  background-color: white;
  border: 1px solid #CCC;
  ...
}

Now .btn-default can be used independently, while still making it easy to re-use the common button styling.

Overly Long Rules

Beware oddly specific rules like this:

div > table tr td > .bold-text span {
  font-weight: bold;
}

What’s going on

In unclean CSS, every rule must be increasingly specific to override all the other rules that accidentally apply. Nicole Sullivan coined the term “specificity wars” to refer to this arms race.

This is a clear sign that the CSS is not organized well, if at all, and needs to be broken into independent modules.

Solution

In other languages we would use modules or namespaces to start to break apart these globals. In CSS we need to move to components, which will be discussed in detail below.

Over-reaching Styles

Watch for deceptively simple rules like this that appear buried in your CSS:

p {
  padding-left: 10px;
}

What’s going on

By modifying a common html tag the developer has inadvertently affected wide swaths of the application. Of course, this is could be the intended behavior, but more likely in trying to align one element they’ve broken many others.

Solution

Use Bootstrap with LESS to override the base variables. This provides a clean way to define global default styling (e.g. text size and spacing). Then define all other styles within a “namespace” class like:

.my-component {

  p {
    padding-left: 10px;
  }
}

This ensures that your styling on <p> tags won’t bleed into other areas.

Re-inventing the HTML Wheel

Examples:

<div onclick="window.open('http://www.my-link.com');">click here</div>
<a href="#" class="btn btn-primary">Save</a>
<div class="select-dropdown"></div>
<div class="select-menu"></div>

What’s going on

These are all classic cases of re-inventing the wheel, or in this case using a combination of HTML, JavaScript and CSS to re-invent default HTML behavior.

The root cause is usually that your design process that is not considering the full set of requirements, such as accessibility and internalization. If your designers work only in PhotoShop they will create flourishes that browsers simply don’t support.

Solution

The solution here is simple: Let HTML be HTML.

Browsers are imperfect, but they do a great job supporting basic behavior like links and forms. More importantly, browsers put a lot of effort into supporting subtle but critical features like accessibility, screen readers, mobile and internationalization. Browsers never put a native dropdown menu off the screen.

One of the most remarkable things I learned when starting DataFox was that by default HTML and CSS do what you want. For example, browsers natively support keyboard navigation if you use a <button> tag for buttons and <a> tag for links. Large companies spend millions of dollars making their products comply with such accessibility rules; it’s much easier to do so from the start.

Similarly, by default HTML handles when your page is translated or zoomed or printed.

The deeper solution is to update your design process to respect the constraints of the browser. This is not an easy habit to change, but a great designer designs for the medium they are working with. After all, industrial designers don’t get to design bridges that would collapse, no matter how beautiful. Don’t let them design websites that will collapse under their styles.

Un-Dead Code

By nature CSS makes it uniquely difficult to know if a rule is still actively used. This code isn’t strictly dead because it’s still reachable, but it’s also not exactly alive and needed. A great sign are comments like this:

/* is this still used? */
/* TODO: okay to remove this file? */

This code not only bloats the size of your files, it becomes a set of landmines which you can easily trigger by accident.

What’s going on

Here there is not a clear interface for your CSS, so it is impossible to predict if and when it is used. Of course, CSS is never used in isolation of HTML, and in most codebases the only defined interface is whatever HTML exists, including dynamically templates. That’s bad.

Solution

As before, the biggest solution is to create a clear interface by grouping CSS into components. This alleviates the worry about un-dead code because unused components are well contained and much easier to search for in the code.

As part of this your html should ideally be organized into components as well, which is fortunately the norm in React and Angular.

Browser-Specific Hacks

This has fortunately become much better since the days of IE6, but you’ll often still see this type of code:

<!--[if IE 7]>
...
-->

Or this snippet from Stack Overflow:

/* For IE css hack */
margin-top: 10px; /* apply to all ie from 8 and below */
*margin-top:10px;  /* apply to ie 7 and below */
_margin-top:10px; /* apply to ie 6 and below */

What’s going on

The problem is not the need to write occasional browser-specific code; the real issue here is that shims and hacks are implementation details that dirty your code.

Solution

First, be very clear which browsers you support, and avoid writing hacks unless necessary. The cleanest code is the code you don’t write.

Second, don’t adopt features before they are broadly supported no matter how fun they sound.

Lastly, use Bootstrap, which will handle 99% of browser compatibility you should face. Occasionally, you may need a third party shim to support legacy browsers. If none exists, take it as a clear sign that there is a simpler way to achieve your goal.

CSS Selectors Used for Both Styling and JavaScript Interaction

$('.btn-default').click(function() {
  // … handle the button click …
});

What’s going on

This is a clear violation of the Single Responsibility Principle because a CSS class (or id or tag) is doing double-duty. The result is that your visual style is tightly-coupled with your interaction. That’s fine the first time, but it makes it painful to re-use the style in a slightly different context. In the above example any button with the default style inherits the click behavior.

Solution

Make a hard-and-fast rule that any selectors can be used for one thing, whether that is styling or JavaScript. The same applies if you use selectors for other tasks like screen readers or browser-based tests (I suggest using a specific data-test tag in this case).

Hard-Coded Values

Just as you should replace “magic numbers” with constants in other languages, you should not accept CSS like:

color: #AEAEAE;
margin-right: 22px;
font-family: Helvetica, Arial;

Such hard-coding leads to inconsistent styles and makes even a simple change to text size or color a painful ordeal.

What’s going on

By default CSS doesn’t provide variables or inheritance, so there isn’t any obvious way to define constants for colors, sizes, etc. Furthermore, in many cases the developer has copied values directly from a Photoshop mockup without regard for existing styles, resulting in a myriad of colors, fonts, and other styling.

Solution

Solving this problem is both easy and hard. The easy step is to use variables in LESS to define baseline styles in one place, and refer to them everywhere. You can even define related variables like:

@text-color: #666;
@text-color-dark: darken(@text-color, 20%);

which is a great example of don’t-repeat-yourself (DRY) code.

The hard step is to rework your design process so designers also abide by these core styles in all sketches and mockups. I’ll discuss this more below.

Duplicated Code

DRY is a great principle for all languages, and it applies in CSS as well. I often see code like this:

.button {
  color: white;
  border-radius: 5px;
  background: blue;
}

.bigger-button {
  font-size: 120%;
  color: white;
  border-radius: 5px;
  background: blue;
}

What’s going on

There are a couple reasons I’ve seen for copy-paste CSS:

  • Lazy or inexperienced developers
  • Not knowing how to refactor CSS so it is reusable
  • Inconsistent design requirements

Solution

The solution depends on the problem.

In the first case, code review and mentorship are the best way to train engineers. CSS code reviews should be every bit as serious as any backend code reviews – after all, broken CSS has a direct impact on your users.

In the second, you should use LESS or SASS to introduce better means of code re-use. For example you can inherit from other styles:

.bigger-button {
  .button;
  font-size: 120%;
}

Using Relative Widths for Alignment

Relative widths are great when used for columns (i.e. “grid” layout in Bootstrap). However, you should never see code like:

margin-right: 10%;

What’s going on

This is a sign of poor engineering, because this style will only look correct on the engineer’s current screen. Furthermore, it’s a sign that the surrounding styles are inconsistent, so there is not an obvious “right” way to accomplish the style.

Solution

First, make testing at multiple screen sizes a clear part of your frontend process. If responsive design is a requirement, then you should catch such fragile code.

Second, CSS components will drastically reduce the number of small adjustments that are required, because components should already support responsive sizing.

Inline Styles

Browsers natively allow you to specify CSS directly in an HTML tag like:

<div style="color: red">

What’s going on

This is a vestige of the earliest days of the web. It’s the frontend equivalent of creating raw SQL from user input – it’s a really bad idea. Here’s why: First, it is by definition a one-off change that won’t be consistent (but will override everything else). Second, it’s bad practice to write one language in another language’s file (even if React has adopted this practice) because it is hard to understand and even harder to perform proper escaping. Lastly, there are security ramifications since improperly-escaped inline CSS can be exploited. Ideally, your site’s Content Security Policy (CSP) should disable all inline CSS to ensure this is not a problem.

Solution

Ideally, update your CSP to block inline styles entirely. Another good option is a lint rule that disables inline styles. At the very least, enforce this rule in code reviews without exception.

!important

font-size: bold !important;

What’s going on

Using !important in CSS like the global keyword in other languages: it’s the tool of last resort. Usually !important is a symptom of unclean CSS and only contributes to the specificity wars.

Solution

I recommend a lint rule that warns (but doesn’t block) !important and catching this in code review.

No Documentation

Sadly this doesn’t need an example, because virtually all CSS out there lacks documentation.

What’s going on

Programmers often assume that since there are no classes or modules, inline comments suffice to document the CSS.

Solution

CSS needs documentation, just like any other language, but it doesn’t have to look like JavaDocs. Instead, create live documentation of CSS components.

A great example is the Bootstrap component documentation which uses the actual Bootstrap CSS file to demonstrate examples of how to use the components.

As you make your own components, add a simple documentation page with HTML and your new CSS. This is the “unit test” for your new component, a place you can easily test it in different browsers and conditions.

DataFox live style documentation
An example of live style documentation at DataFox

But it also becomes the live documentation for your components and how to use them. Rather than publishing a style guide on the wiki that no one will read, put any guidelines right in the documentation (e.g. use this tooltip for quick suggestions, or a modal for complex interactions).

Missing Tests

Wait, how do you write tests for CSS? Obviously, there is no CSSUnit. But un-tested CSS still breaks and it still costs your company lots of money and credibility.

What’s going on

Most CSS is difficult to test for the same reason most code is hard to test: it is not modular and lacks clear interfaces. In most advanced applications there are a large number of screens, some of which only occur in edge cases like errors or legacy accounts.

Solution

By breaking your CSS into modular components, you can unit test each one in isolation in your live documentation page. Testing can still be manual in different browsers and conditions; the key is that you are confident that it will work in all cases. Eventually, you can automate your tests using visual diffs of browser-based testing.

Legacy Hacks Left in Good Code

Inevitably, your CSS will have some leftover hacks that you simply can’t remove or refactor yet. For example, to style Marketo forms, we have to rely heavily on !important to override their original use of !important.

What’s going on

In this case, the inelegant code is a necessary evil, but it is polluting the clean code around it and risks encouraging other developers to follow this pattern.

Solution

I recommend keeping these rules in a hacks.css file. By clearly labeling these as hacks, you deter others from copying them. This also keeps your code modular by containing the hacks to one place.

Conclusion

Bad CSS is not an inevitable part of frontend coding. By watching for these code smells you can write maintainable, clean CSS even as your application evolves and the codebase grows. Hold your CSS the same coding standards you apply to your other code, and you’ll be amazed at the benefits to your development speed and the design consistency of your site.