Shape brain Slice 1Created with Sketch. Slice 1Created with Sketch. Slice 1Created with Sketch. menu 1433522600_bubble + 1433522600_bubble copy Thumbs up icon 15

Taming enterprise-scale CSS

Written on: Nov. 5, 2015

Last year, Mark Otto from Github wrote a popular blog post on how they go about architecting their CSS. Inspired by this spirit of knowledge sharing, other designers and engineers have since followed suit, including:

I particularly enjoyed reading these articles as they give a warts-and-all peek into how other companies are tackling the challenges we are all facing on a day-to-day basis as front-end developers. Given the sheer volume of new technologies and methodologies appearing daily on Twitter, it is hugely useful to find out which techniques people are actually using on real code bases within projects that have all those annoying things like budgets, deadlines, and browser support requirements.

With that in mind I want to throw my hat into the ring and share how we do things on Asset Bank–our web-based digital asset management system used by organisations ranging from small local councils looking to best manage their digital assets, through to large multinationals such as Coca-Cola and Unilever.

Screenshot of Asset Bank

Asset Bank is a very long-lived system, having come into existence around 10 years ago. Dig deep enough into the CSS and you’ve a good chance of uncovering some archeological relics such as IE6 fixes. That said, we have been making ongoing efforts over the last couple of years to knock the CSS (and the tooling around it) into shape.

What follows is by no means the definitive way to do CSS (we are always looking to improve), but it works for us pretty well, and hopefully you will find something useful to take away.

Preprocessor

The first thing we did was get a preprocessor involved. We had used Sass on some other projects and loved the way it made modularity in your CSS so easy, and encouraged consistency and DRYer code with variables and mixins. A big-bang rewrite of the CSS wasn't on the cards, so we needed to cope with all the legacy CSS and provide a framework for an ongoing transition to shiny happy CSS. We opted for the following directory structure to organise our Sass files:

/base/ - contains typography styles, normalise, basic layout etc
/legacy/ - all the old CSS files were renamed as Sass partials and filed away in here
/modules/ - reusable bits of UI, e.g.buttons, dropdowns, modals etc.
/util/ - a place for mixins and utility classes

The idea is that, over time, the contents of the legacy directory will dwindle and the modules directory will grow. All of the Sass files compile down to a single main.css with minified output, which gives us a nice reduction in http requests and page weight.
At the moment, the compiled CSS file is added to Git, with the reasoning that back-end developers don't need to touch any of the Grunt/Sass stuff if they don’t want to, and it saves a step in the already lengthy building-an-Asset-Bank-release process.

In hindsight, this hasn’t really worked that well.

We do all of our work in Git branches and, with the Sass being compiled down to a single-line CSS file, you have a guaranteed merge conflict if both branches have touched the CSS in any way. Our current solution is to have a little script that developers can run to regenerate the CSS file and do the necessary git remove and git add commands. Not perfect, but it seems to be keeping everyone happy for now.

We opted for the libSass flavour of Sass via grunt-sass, because it’s faster and we didn’t want the Ruby dependency of the original Sass in a Java project. At the time, libSass was missing proper support for the @extend feature of Sass, but we figured we could live without that for the time being (support has since been added). We’ve also configured source maps to be generated, but have noticed some ongoing flakiness with them, and found them less crucial than we originally thought.

Autoprefixer

Another bit of tooling we added a while ago was grunt-autoprefixer. If you’re not already using this, then I urge you to look into it. It doesn’t take long to set up and once done your team never have to think about vendor-specific prefixes when writing CSS again (goodbye css3please.com). 

Linting

We use grunt-contrib-CSSlint to lint compiled CSS rather than linting the source scss files (this is because there is no libSass compatible tool to lint scss files - hopefully one is on the way). Obviously linting legacy CSS code generates too many errors to be useful, so we set up two levels of linting by generating two separate CSS files specifically for linting:

  • main-lint.css — this contains all the legacy and base styles and is linted with a relaxed configuration (it catches uncontentious things like syntax errors, duplicate properties, zero values with units etc).
  • modules-lint.scss — this is where the shiny new CSS goes and is much more strictly linted (no ids, no !important statements etc).

We’re still playing around with the configuration of both linting jobs, trying to strike a pragmatic balance between encouraging code quality but avoiding dogma.

Grunt setup

We already had Grunt in the project as we use it for our automated JavaScript tests (which is another blog post in itself) so it was the natural choice to help us with our Sass workflow.

Our Grunt setup is very simple, we have a grunt watch command that listens to any changes in scss files or js files. If a scss file changes, then it compiles the CSS and runs autoprefixer against it. This currently takes around 1.6 seconds on a macbook pro, which is quick enough to not be annoying during development.

We don’t run the the CSS linting on every save, as it would slow things down too much. There is a separate grunt lint command that runs both the CSS and js linting.

Minification and concatenation of other front end assets is handled outside of Grunt by a Java-based tool called JAWR.

Continuous integration

We have a front-end specific Jenkins job that runs the CSS linting, does some JavaScript linting (using jshint) and runs the JavaScript tests on every push to git. If any of these fail then you have broken the build. 

Code style and architecture

I was lucky enough to attend a SMACSS workshop with Jonathan Snook a few years ago which switched us on to thinking about CSS in a more modular way. We have since established a simple BEM-style naming convention that we insist on using for all new CSS in Asset Bank:

Naming conventionNotes
.module-nameAim for one word, but if it needs to be longer, separate words with a single hyphen. E.g. .button, .mega-menu
.module-name__elementE.g. .dropdown__list
.module-name--modifierE.g. .button--large
.module-name.is-activeState classes are typically related to JavaScript behaviour and are attached to a module (i.e. there should never be a global '.is-active' class).
.js-classnamePurely a hook for JavaScript behaviour and should never have any associated styles. E.g. .js-dropdown

Like most people we started off loving nesting in Sass; we’re now learning its downfalls, as it results in higher specificity than you really need. The way we write CSS is something we are looking to improve all the time, and get a lot of value out of reading stuff from the likes of @csswizadry and @necolas.

We have some rules about our CSS coding style written down:

  • Class names are hyphen-separated (not camelCase).
  • ID's should be hyphen-separated.
  • Opening curly bracket on same line as the rule set.
  • Indentation is 4 spaces (no tabs).
  • Each property on its own line.
  • Insert a space after the colon.
  • Colour declarations should be lowercase hex values (unless using rgba) using the short form where possible.
  • If you have multiple rulesets separated by commas, then each ruleset should appear on its own line.

Style guide

We have a living style guide that is available on a url in the app, and gives examples (with code samples generated by a handy JavaScript library code-print) of the different UI components available in Asset Bank.

Screenshot of Asset Bank style guide

This has been a big help in making the user interface of Asset Bank more consistent, encouraging UI reuse rather than reinvention. I want to extend this to also document the available JavaScript components in Asset Bank to avoid duplication of js code.

Other fun facts

  • We don’t use a CSS framework–they didn’t really exist when Asset Bank was created and we’ve never felt the need to add one. Though that doesn’t mean we don’t borrow ideas from frameworks like bootstrap or foundation on a regular basis.
  • We only have a couple of Sass mixins: one for media queries (which means all the breakpoints are defined in one file), and one for adding clearfix styles.
  • We support IE8, though I’m personally of the view we shouldn’t continue doing so for much longer as it is starting to hold us back in terms of CSS features.
  • We use a mixture of pixels and ems. I’m keen to switch everything to rems whenever we drop IE8.
  • We use an icon font, though having played with svg icons on another project I’m interested in making the switch (I’m looking at you again, IE8).
  • We use pull requests a lot in our workflow to get a second pair of eyes on the code.

Looking to the future

In a system developed over many years by many different people, there are lots of refactoring opportunities in the code. One thing I want to look at is ways to encourage us to get stuck into that technical debt and continue converting the legacy CSS into shiny new CSS. I was inspired by hearing in some recent conference talks that at Github they track various metrics on their CSS over time. Along similar lines, I am currently looking at using tools such as parker to track things like the number of IDs, number of !important statements and total specificity over time. That way we will have a set of graphs, visible to everyone, that we can get moving in the right direction.


Written by