by Adam Brett

How I Do CSS

This article was published on Thursday, July 31, 2014 which was more than 18 months ago , this means the content may be out of date or no longer relevant. You should verify that the technical information in this article is still current before relying upon it for your own purposes.

Inspired by Mark Otto's tour of GitHub's CSS, Ian Feather's tour of Lonely Planet's CSS, and Chris Coyier's tour of CodePen's CSS, now seemed like a good time to write a post on CSS architecture I've been putting off for far too long (I first commited the draft of this post 2 years ago).

My take will be slightly different, as well as looking at the CSS on a single product (which I do at my day job), I am going to cover how I do CSS on my consultancy projects (which is the style and architecture that we are starting to adopt at my day job too), and I'm going to include code examples.

Quick Facts

  • [1] I use SCSS.
  • [2] I don't use any frameworks for front-end work.
  • [3] I don't write any CSS for back-end work.
  • I use Autoprefixer rather than mixins for vendor prefixes.
  • [4] I use 3 Make targets for building.
  • I use [5] Atomic Design, [6] BEM and [7] SMACSS.
  • I [8] namespace all of my classes.
  • [9] I prefer pixels over all other units of measurement.
  • [10] I prefer normalize.css to reset.css.

Preprocessing

If you had asked me 18 months ago, I would have told you to use LessCSS over SCSS. The truth is, at that time the technologies were probably about equal, but I had been using LessCSS for longer as prior to that, LessCSS was a better product.

Now things have changed, and I've been using SCSS almost exclusively (except for legacy LessCSS projects) for around 12 months, and it's awesome. Even though the syntax is almost identical for both, I do prefer SCSS's way of doing things.

As a side note, Twitter Bootstrap, which is written with LessCSS started maintaining an official SCSS port a few months ago. I would hazard a guess that for the next major version, SCSS will become the primary language for Bootstrap.

Frameworks

It should be noted that there are two types of (S|Less)CSS framework, and each has a very specific use-case:

  1. UI Component Frameworks
  2. Libraries/Authoring Frameworks

UI Component Frameworks, such as Twitter Bootstap, Zurb Foundation, UI-Kit, and Semantic-UI (all of which I use in various scenarios), are highly opinionated, and provide pre-build components, colours, and styles.

Libraries and Authoring Frameworks, such as Compass, Bourbon, and InuitCSS will provide structure and mixins to make writing CSS easier, but are un-opinionated with regards to style and ususally don't contain pre-built UI Components.

There are three main types of product that I maintain both in my day job, and my side work as a consultant:

  1. Front-end (public) websites
  2. Back-end (private) applications
  3. Blackbox/SaaS Applications (public)

Front-end Systems / Public Applications

If I'm building a public facing website, which includes SaaS or Blackbox Applications, I will not use a framework of any kind. A lot of people would use a Library or Authoring Framework for this, and they are well suited to it, however I've rarely found a situation where the added complexity and learning curve is worthwhile. Once you get over the basics (like setting up a grid) and strip away the vendor prefixing (which I use Autoprefixer for) there is little of value left in these frameworks.

Back-end Systems

For back-end applications, I try to never write a line of CSS. No-one is going to care what these systems look like. The most important factor is that they're functional and easy to use. This is a perfect example of when to use a UI Component Framework. A great deal of work has gone into making these component frameworks and it would be a waste of your time and a waste of your clients money if you tried to replicate it.

To make a decision about which component framework I'm going to use I will generally take a look at the components each provides and see which is best suited to the application I'm going to be building (for example, UI-Kit contains a pretty nice Markdown Editor). If they're all pretty much the same, I will choose Twitter Bootstrap, because it is fairly ubiquitous and contains most things you'll need for the majority of back-end systems. There are very few developers who won't be familiar with Bootstrap, so the next developer to come along will thank you for using it.

Build Process

It seems like there is a new front-end build tool every other week. The most popular two are Gulp and Grunt. I cannot comment on either, as I have never used them. All of my building is done via Make. In-fact, the only time I don't use Make is when I have to work on Java code, when Ant is far easier to get working.

First, I have a single SCSS file for all of my imports. I usually call this something along the lines of "site.scss", "app.scss", "style.scss", "all.scss". I'm not very original, nor very consistent.

Sometimes I will add a second SCSS file if a different part of the site requires significantly different styles to another and want to share the SCSS mixins/library (e.g. marketing site + blog), but usually prefer to avoid this in all but the most basic of situations, as it indicates it should really be three separate projects: a marketing site, a blog, and a shared SCSS library.

The CSS output by the build process is never included in version control, which makes merging a whole lot easier. Instead it's generated as part of the standard build and deploy process for a site.

I start my Makefile by setting some variables to make life a little easier:

SASS = sass --no-cache
PREFIXER = autoprefixer

THEME_DIRECTORY = ./public

CSS_DIRECTORY = ${THEME_DIRECTORY}/assets/css
SCSS_DIRECTORY = ${THEME_DIRECTORY}/assets/scss

SCSS_FILE = ${SCSS_DIRECTORY}/style.scss
CSS_FILE = ${CSS_DIRECTORY}/style.css

First, I set the SASS variable to the location of my sass command. I add --no-cache to all sass calls to stop it leaving build artifacts all over the place. Then tell it where to find autoprefixer and what files and directories we need for building the CSS.

I then have three SCSS build targets that I like to use in my Makefile, all of which depend on a single css-dir build target which creates the css directory:

css-dir:
    @mkdir -p ${CSS_DIRECTORY}

This is nice and simple, it ensures the css directory is created, as it's not commited to version control. I use @ at the start of the command to suppress the output. This build target should only ever be called by other build targets.

The SCSS build target (make scss) is the most basic. It simply builds the SCSS file, minified with standard settings, outputs it to the css directory, and finally runs autoprefixer. This is the build target used for the code that gets deployed.

scss: css-dir
    ${SASS} --style compressed ${SCSS_FILE} ${CSS_FILE}
    ${PREFIXER} ${CSS_DIRECTORY}/*.css

The next build target (make scss-pretty) is for debugging.

scss-pretty: css-dir
    ${SASS} --souremap ${SCSS_FILE} ${CSS_FILE}

This just generates the CSS as normal, but adds a sourcemap and doesn't minify anything. Sourcemaps are kind of a big deal, and if you aren't using them, you probably should be. They basically allow your browser to map styles back to the original SCSS file, rather than the generated CSS file. This is amazing for debugging.

The last build target I use is for watching for changes (make scss-watch). This will simply watch my SCSS files for changes then automatically re-build the CSS file.

scss-watch: css-dir
    ${SASS} --watch --sourcemap ${SCSS_FILE}:${CSS_FILE}

I also add the --sourcemap flag here to generate the sourcemaps as I'm only ever going to use this target in development.

Atomic Design

Atomic Design is all about how you construct pages, it ties in really well with BEM and SMACSS, and it goes something like this:

  • Elements are the smallest possible thing that can be styled. You can't break a HTML Element down into anything smaller (let's ignore Web Components and Shadow Dom for now).
  • Elements are combined to form Molecules. A Molecule is the smallest possible stand-alone part of a Component. For example, a <h1> tag and a wrapping <div>.
  • Molecules are combined to form Components or Modules.
  • Modules are combined to form Templates/Layouts.
  • You add content to Templates to create Pages.

I have found this simple method of construction of pages, always building on the smallest part by combining it with others, leads to much more consistent and easier to maintain sites and applications.

BEM

BEM stands for Block-Element-Modifier, and it's a way of naming your CSS classes that gives them more meaning and transparency. The naming conventions are simply:

.block {}
.block__element {} or .block--modifier__element {}
.block--modifier {} or .block__element--modifier {}
  • .block represents the highest level of abstraction for a component, it's usually a wrapper of some kind.
  • .block__element is a logical section of your component.
  • .block--modifier is a variation on the default behaviour.

For example, a .box component might have .box__header, and there might be an alternative header for featured boxes, which you would write as .box__header--featured or box--featured__header depending on whichever is more semantically correct in your situation.

You should try, as much as possible, to design Modules so they can be combined for greater effect. For example, a .box module with a .box__body could be placed in a sidebar or in the main content, and should behave correctly automatically, reflowing as necessary. The .box__body could also very well contain a navigation list module, a featured content module, an advertising banner module, and so on. This should all work without any hacks or extra code.

A quick note on Modifiers: They should only ever build on the base style, they should not replace them entirely, which means your HTML would look something like:

<div class="box box--featured"></div>

And not just:

<div class="box--featured"></div>

BEM and SCSS

SCSS has some nice support for BEM built-in, which means you can write your classes nested:

.box {
    &--featured { ... }

    &__header {
        &--highlighted { ... }

        ...
    }
}

And SCSS will work out the correct names and CSS structure for you.

SMACSS

SMACSS, to me, is a loose philosophy for the separation of your CSS into logical sections, and a related directory structure. It really is that simple. It looks like this:

  • Base - Reset, HTML Element basic styles (normalisation).
  • Layout - Columns, grids, etc.
  • Modules - All of your Modules, one per file.
  • States - Classes that globally describe an object, and may change (e.g. .is-hidden).
  • Themes - Colours, fonts etc. Optionally actual themes (like Gmail).

In reality, I've not found much use for Themes, it's very rare to have an actual theme-able application like Gmail or whatever. For me, this translates to something similar to the following in practice:

.
├── base
│  ├── _base.scss
│  ├── _normalize.scss
│  └── _variables.scss
├── layout
│  ├── _2col-left.scss
│  ├── _2col-right.scss
│  ├── _3col.scss
│  ├── _footer.scss
│  ├── _grid.scss
│  ├── _header.scss
│  └── _sidebar.scss
├── mixins
│  ├── _clearfix.scss
│  └── _magic-ratio.scss
├── modules
│  ├── _accordion.scss
│  ├── _box.scss
│  ├── _buttons.scss
│  ├── _modal.scss
│  └── _nav.scss
├── states
│  ├── _responsive.scss
│  └── _visibility.scss
│── themes
│   ├── _colors.scss
│   └── _fonts.scss
└── site.scss

Breaking this down, each file contains:

.
├── base
│  ├── _base.scss

This is the base styling for all HTML Elements based on my theme, it builds on _normalize.scss.

│  ├── _normalize.scss

This is a reset that makes things look the same across all browsers.

│  └── _variables.scss

This file contains global variables and settings that apply across all files, e.g. $font-size, $line-height, and $brand-color.

├── layout

This directory contains files of three types:

  1. Grid

    This is a traditional web grid and related classes, might be responsive, might not, depends on the project.

  2. Header/Footer/Sidebar/Misc Layout Element

    Specific styles for layout elements that don't really relate to the rest of the content on the page, and won't be re-used (if it were re-usable, it would be a Module).

  3. Layout Files

    There are only really 4 types of web layout:

    1. Single Column
    2. 2 Column Left
    3. 2 Column Right
    4. 3 Column

Everything else is a variation on these in some way, I include files for all of the types that our site/app uses here.

├── mixins

Any mixins or placeholders that aren't module specific go here. I tend not to need to use mixins that often, so I only have one or two.

├── modules

This is where you'll do the bulk of your CSS work. All of your modules should live in this directory, each in a self-contained file. You should probably name the file the same as your .block class for consistency.

├── states

I don't often use this directory. When I do I tend to break it down into types of state, i.e. based on what triggers the change, like responsive classes e.g. .hidden-desktop or .visibile-mobile. These classes should be global and work on all elements, with all modules. Modules may have their own state classes that are specific to the module as well.

│── themes
│   ├── _colors.scss

Like I said, I don't have much use for themes, I generally have a _colors.scss which includes all of the colours I use across the site, I then reference the variables in the modules/elsewhere so colours only never need to be changed in a single place. I'll also alias some colors in _variables.scss in the base folder to give them more descriptive names (e.g. $brand-color: $blue).

│   └── _fonts.scss

As with colours, I include fonts in variables here and then use the variables in modules and other files, adding more specific aliases in _variables.scss when appropriate (e.g. $logo-font: $serif-font).

└── site.scss

Finally we have site.scss. This is the only file that SCSS will actually compile (all others are prefixed with an underscore to stop SCSS compiling them - it ignores all such files). It looks something like this:

@import "themes/colors";
@import "themes/fonts";

@import "base/varialbes";

@import "mixins/clearfix";
@import "mixins/magic-ratio";

@import "base/normalize";
@import "base/base";

@import "layout/grid";
@import "layout/header";
@import "layout/footer";

@import "modules/box";
@import "modules/buttons";

@import "states/responsive";
@import "states/visibility";

This file contains only imports, no code. Import order is important as mixins and variables need to be defined before you can reference them. There's nothing else special about this file, other than to say when importing in SCSS you can skip the _ at the start of the filename and the .scss at the end, SCSS will add those automatically.

Namespacing

When writing my CSS classes, I always namespace them with a single character. Usually the first character of the name of the company I'm working for. This means there is almost no chance of our class names clashing with anything we include. For example:

.v-box {}
.v-box--featured {}

or

.j-nav {}
.j-nav__link {}
.j-nav__link--active {}

Units

Once upon a time, browsers were terrible at resizing websites. To get around this, we started to use a relative unit for measurement, and came up with all sorts of hacks to make this work with our brains (62.5% font-size, anyone?). That's not the case any longer. Use px or rem as much as you like. I prefer px because it's more familiar, but rem is just as easy (although not as widely supported).

Normalize

I prefer normalize over reset. I don't like to remove things only to re-add them later, it seems wasteful.

Refactoring

I take a very pragmatic approach to refactoring legacy CSS that doesn't follow my modern standards. When you touch it, modernise it, otherwise, leave it alone.

The Future

  • I'm not linting right now, but will be on my next project (using scss-lint).
  • I'm looking to start CSS Testing on a future project, but I haven't looked at it in depth yet.

For exclusive content, including screen-casts, videos, and early beta access to my projects, subscribe to my email list below.


I love discussion, but not blog comments. If you want to comment on what's written above, head over to twitter.