Sensible CSS

Every developer can write CSS, the hard part is keeping things manageable as your app starts to grow.

Over the past two years I've been writing CSS for several large web applications. I've been fortunate enough to be the primary maintainer of these code bases, which has given me free reign to try out various approaches in an attempt to keep things sensible. In this post I will will share some of my techniques which I feel really made a difference.

1. Preprocessors

If there's one single thing that really made my life easier it's preprocessors. We chose to use SASS on the projects I worked on. I had used LESS in the past, but chose to start using SASS mainly because it's all I ever heard other CSS devs talk about, and also due to this post by Chris Coyier.

Aside from which preprocessor you choose to use, I will become your closest ally in helping you to fight the battle for sensible CSS. The tips that follow will assume that you are using a preprocessor. Examples will be in SASS, but most should be easily transferable to other languages.

2. Standard padding

Throughout our app we need to use padding and margins to space things out. In apps I've worked on in the past these values have all to often just been random pixel values that looked close to the mockup at the time.

Keep all spacing in your app looking consistent by introducing padding variables. I like to keep these 'app wide' variables in a single file called vars.scss.

$padding: 14px;
$padding-200: $padding * 2;
$padding-150: $padding * 1.5;
$padding-75: $padding * 0.75;
$padding-50: $padding * 0.5;

Throughout your app make it a rule that every padding or margin definition must use the standard variables.

.header {
  padding: $padding;
  margin-bottom: $padding-200;
}

If you're targeting relatively modern browsers, or aren't using a pre-processor, you could also look at using rem units to do something similar.

html {
  font-size: 14px;
}

.header {
  padding: 1rem;
  margin-bottom: 2rem;
}

3. Standard colours

Every professional application has a colour pallet. Don't bother typing those horrible hex codes all over your app. Instead define them in once inside your vars.scss file and use the variables throughout your app code.

$color-blue: #4781AA;
$color-blue-dark: #002F5F;
$color-dark-text: #333;
$color-medium-text: #505050;
$color-light-text: #777;
$color-pink: #FFF1E0;
$color-claret: #9E2F50;
$color-pink-tint-1: #F6E9D8;
$color-pink-tint-2: #E9DECF;
$color-warm-grey-1: #A7A59B;
$color-warm-grey-2: #74736C;

It's now a breeze if your designer wants to alter the color pallet slightly.

Nesting

With CSS preprocessors comes the ability to nest selectors. With all new things, it's easy to get carried away. I like to keep it to a minimum, attempting not to nest deeper than three levels. I name my classes in a way that means that I can write really short, specific selectors.

This is good for selector matching performance (negligible) and means elements remain portable. By 'portable' I mean that if markup changes slightly, you don't have to go through altering selectors that were previously very specific, so that they match the new document structure.

File structure

The first step to sensible CSS is keeping files as short as possible. I want each file to cover a very specific part of my application, there should be no rules inside that file that do not concern that part.

Let's have a look at a fictitious header.scss file as an example.

.header {
  background: red;
}

I know what you're thinking: 'this is ludicrous, an entire file for one CSS rule!'. Relax...this this is fine! We're using a preprocessor that will combine all these files into one file, so there really is no overhead to creating more files. I've worked on projects with 100 - 150 .scss files, and it was one of the most effective ways of keeping things manageable.

What we're getting here is 'separation of concern', and this is super valuable! Although right now our header.scss file is really short, who knows how it will evolve over the course of the applications life. Whenever I start styling a new part of an application, the first thing I do is create a new file for it. The more files you have, the better!

Selector order

Not all the files in you application are going to be as short as header.scss above. When files do begin to grow, it is important to structure them consistently so devs know where to find rules. To help with this, I stick by the simple convention of ordering my selectors in the same order that they appear in the markup.

If the markup looks like this,

<header class="header">
  <ul class="header_links">...</ul>
  <input class="header_search" type="search" />
</header>

... the corresponding stylesheet should look like this,

.header {}
.header_links {}
.header_search {}

Styling state

At some point you're going to have to override the default styling of an element when your app is in a specific state. 'State' could be anything, but I'll use the simple example of a disabled button. Our default styling for the button is active, then we have a selector that overrides the default appearance to give it a 'disabled' appearance. One way of structuring this that I'm sure you've all come across is this:

.my-button {
  background: red
}

/* 1000 lines later or in another file */

.my-button[disabled],
.my-other-thing[disabled] {
  opacity: 0.5;
  pointer-events: none;
}

This is a nightmare. If I've be given the task to work on .my-button I want to go the the initial selector and see all the possible states that element can be in, in one place. I am then able to make informed judgements when taking care of my task.

/** My Button
 ---------------------------------------------------------*/

.my-button {
  background: red
}

/**
 * [disabled]
 */

.my-button[disabled] {
  opacity: 0.5;
  pointer-events: none;
}

I also included a handy divider that gives pre-warning to other developers that I'm defining a stateful override based on the [disabled] attribute.

I wrote a more detailed post on 'Styling state in CSS'

JS only classes

One of the tricky things about classes is that they may not only be being used for styling, they could be being used in JavaScript too. This can make refactoring markup a daunting process, you want to tidy everything up, but changing a class name that JavaScript is dependent on could take down your whole app.

A convention of 'Javascript specific classes' that Nicolas Gallagher wrote about has proven to work really well. In our markup we have two types of classes, styling classes and JavaScript classes. It is clear when a class is a JavaScript class as it is prefixed with js-.

<input class="header_search js-search" type="search" />

When were want to select the element in JavaScript we use the js-search class.

var el = document.querySelector('.js-search');

In CSS we use the un-prefixed class. Styling using the js-* classes is strictly forbidden.

.header_search {
  /* styles */
}

Avoid 'globals'

In JavaScript it is common knowledge that polluting the global scope with variables is bad practice, in CSS it is less well known. I've inherited projects where I have spent months battling against global styles, let me give an example,

button {
  margin-bottom: 20px;
}

By writing the above CSS the developer has made the assumption that every <button> element in the application must have the following styles. Now every-time I want to use a button element I find myself reseting these styles.

.my-button {
  margin-bottom: 0;
}

If we have third-party widgets in our app, they too will be impacted by these global styles.

I usually try to avoid tag selectors, but if they must be used, I use a direct-child selector (>) to ensure these styles don't leak where they're not wanted.

.my-container > button {
  margin-bottom: 20px;
}

Summary

We've all experienced a CSS nightmare at some point; it takes discipline and conventions to keep everything sane. I've tried a lot of things, some worked, some didn't. In this article I've tried to distill 'the best bits'. Drop a comment if you've discovered techniques that help maintain your sanity.

comments powered by Disqus