Self-importance. Entitlement. Snobbery.

Series Web design basics

Organizing your CSS

Jan 30

The larger your website or application gets, the harder it is to keep your CSS sane and easy to maintain. The trouble, of course, is that there is no One True Way. Lots of people have made recommendations on how to do this over the years. Here’s mine.

Let me say this first: I subscribe to neither the SMACSS nor BEM schools of thought, though I agree more with the latter than the former.

My philosophy is that if you have a reasonable file organization, a structured commenting strategy, and strive always to use semantic names, you’re in pretty good shape. No complicated naming conventions necessary.

Use a CSS pre-processor

Nowadays, even on small projects, I use a CSS pre-processor—Sass is my favorite. They’re way more powerful than CSS, are conducive to organizing your code in smaller, topical, maintainable chunks, and just all around make your life easier. If you aren’t using a pre-processor and you build websites on a regular basis, consider switching.

File hierarchy

Here is how I organize the styles in all my web projects:

styles
├── basics
│   ├── _colors.scss
│   ├── _grid.scss
│   └── _typography.scss
├── shared
│   ├── _alerts.scss
│   ├── _footer.scss
│   ├── _forms.scss
│   ├── _foundation.scss
│   ├── _header.scss
│   ├── _mixins.scss
│   ├── _page.scss
│   └── _pagination.scss
├── _feature-1.scss
├── feature-2
│   ├── _feature-2-component-1.scss
│   └── _feature-2-component-2.scss
├── _component-1.scss
├── application.scss
├── ie.scss
└── print.scss
  • basics/ contains the building blocks of the styles: the colors (more on this in just a bit), the typography, the grid.
  • shared/ contains application-wide styles like header, footer and form styles. Default styles for the basic HTML tags should also be included here.
  • The rest of the styles should be organized by feature or component, with more complex features getting their own subfolders.

    • Make sure that every component or feature/page in your application gets its own root-level class—sometimes this goes on the body tag. Then in your style definitions, make sure to namespace the sub-feature or component styles inside the root-level class for better cleanliness and easier overriding of global styles:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <!-- … -->
    </head>
    <body class="feature-1">
      <!-- … -->
    </body>
    </html>
    
    1
    2
    3
    4
    // In _feature-1.scss
    .feature-1 {
      // …
    }
    
  • As is the convention in Sass, all partials should have their names preceded by an _ (underscore) so that the Sass compiler knows not to compile them as individual files in the output directory.

  • application.scss, ie.scss and print.scss are manifests including some combination of the partials. Because they lack the _ prefix, they will be compiled into the output directory.

Colors in Less and Sass

One of the most irritating and error-prone things to have to do in CSS is change colors. When I put together my _colors.scss color manifest, I do a few things to minimize the pain of changing colors, which can happen often in the early days of a web project.

  • Never use raw hex or RGB values outside the color manifest.
  • Every hex value should be assigned a human-readable color name: $tangerine-yellow is much more understandable than #ffcc00.
  • Further, assign those colors to a semantic variable name based on usage:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Main palette
$raw-steel       : #3f3c3a;
$sand            : #dfd2c9;
$aqua            : #cbf1e3;
$orange          : #ff7f00;
$saddle-brown    : #844421;

// Secondary colors
// …

// Greys
$warm-grey       : #c5bdb8;
$light-grey      : #d0d0d0;
$ash             : #e1dbd7;
$white           : #ffffff;

// Backgrounds
$page-bg         : $sand;
$content-bg      : white;
$accent-bg       : lighten($ash, 10);

// Text
$body-text-color      : $raw-steel;
$secondary-text-color : $saddle-brown;

Then elsewhere, you can do:

1
2
3
4
body {
  background-color: $page-bg;
  color: $body-text-color;
}

If later on I want to change the page background or body text color, I only need to change it in the color manifest, and it’ll get propagated everywhere with no fuss.

Commenting

Establish and consistently follow a commenting strategy. Even if you’ve broken up your styles into files, each file will likely have subsections. Do your future self and your team a favor by delineating sections of code with comments.

Here’s how I like to do it. You can see that the names for subsections are indented further than the top-level styles, and that if comments are necessary, they’re included under the header.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*-----[ foundation ]-----------------------------------------------------------
| Reusable content styles. Define your application-wide styles, like links,
| headers, body text, clearfix, etc. here. See the readme for information on
| recommended rule order.
*/

/*----------[ table of contents ]-----------------------------------------------
| tags
| non-semantic styles
*/

/*----------[ tags ]------------------------------------------------------------
*/
// …

/*----------[ non-semantic styles ]---------------------------------------------
| Purely presentational styles with non-semantic names.
*/
// …

Semantics. Semantics. Semantics.

Years ago I hopped on the semantic markup bandwagon and never got off. I’ve developed a bone-deep loathing of purely presentational identifiers in markup and do everything short of twisting myself into a pretzel to avoid using anything besides clearfix in my HTML classes.

“But,” I hear you saying, “what if I need to define a common set of styles that I want to reuse? Wouldn’t I just create a class and use it everywhere?” My answer to that is: only if you’re using vanilla CSS.

If you’re using a pre-processor, as most projects of significant size do, I recommend you put those reusable styles in a mixin. To me, this:

1
2
3
4
<form id="sign-up-form" action="…">
  <!-- … -->
  <input class="sign-up" type="submit" value="Sign up">
</form>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@mixin cta-primary {
  // …
}

@mixin cta-large {
  // …
}

#sign-up-form {
  // Assuming a Susy grid of 12 columns
  @include span(6);
  margin-left: span(3);

  .sign-up {
    @include cta-primary();
    @include cta-large();
  }
}

is infinitely preferable to markup littered with purely-presentational names like:

1
2
3
4
<form id="sign-up-form" class="col-md-6 col-md-offset-3" action="…">
  <!-- … -->
  <input class="btn btn-primary btn-large" type="submit" value="Sign up">
</form>

The former example achieves a much clearer separation of content and presentation, and makes the markup much, much easier to read and understand.

Order CSS rules consistently

Follow a consistent order when writing CSS rules. What the order is is largely immaterial, as long as you and your team agree on and consistently follow that order. Here’s the order I prefer. To make doing this easier, I use CSScomb to automatically reformat my CSS according to a config file.

Coding standards

Generalizing from the last section: establish coding standards for your team and enforce them in code reviews and automated testing, to keep your code base clean and maintainable. Here are the standards I use with my team.

Wrapping up

To summarize:

  1. Use a CSS pre-processor.
  2. Break up your styles into files by common purpose: basic styles, shared styles, then individual features or components.
  3. Structure your color manifest for maximum reuse and minimal cost of change.
  4. Establish and follow a commenting strategy to make your files easier to scan.
  5. Make the names in your markup meaningful. Minimize—strive to eliminate—purely presentational identifiers in your markup.
  6. Order your CSS rules consistently. Consider using a code formatter to help with this.
  7. Establish and enforce coding standards.