A few months ago, a number of designers and UI engineers, inspired by a popular blog post from GitHub’s Mark Otto, published articles on their approaches to writing and organizing CSS. In this post, I’m going to follow suit and take you through a breakdown of the tools we use, as well as the architecture of Bugsnag’s CSS. There’s no surefire method for organizing CSS for all situations; the content that follows is not a guide for writing awesome CSS. However, the better we understand the tools, techniques, and philosophies being used, the better we can tailor our approach to the unique needs of the projects we work on.
CSS Preprocessing is at the center of everything we do in CSS. If you’re not familiar with CSS preprocessors yet, or aren’t convinced they’re for you, take a read through this article. Of the two most popular preprocessors available to work with, LESS and Sass, I’ve chosen to work with Sass. The differences between LESS and Sass are important but nuanced, so I’ll save an analysis of “Why Sass over LESS” for another post. Suffice it to say we’re taking full advantage of advanced features and other niceties that only Sass can provide us with.
In addition to preprocessing in Sass, we make use of Autoprefixer. As its name suggests, Autoprefixer lets you forget about vendor prefixing altogether—you simply write standard CSS and it automatically includes prefixes using data from caniuse.com. It’s worked flawlessly for us and I’ve found it to be cleaner than using the proprietary syntax provided by mixin libraries like Compass or Bourbon.
With concerns around browser prefixing taken care of by Autoprefixer, the Sass utilities that we’ve included in Bugsnag’s website are lightweight and focused on specific needs that we have.
Here’s a partial list of helper mixins, functions, and boilerplate that we’re using:
I’ve taken a similarly cautious approach to introducing open source UI components in Bugsnag. In situations where it makes sense to bring 3rd party UI components into Bugsnag, I tend to exclude their bundled CSS to avoid writing overrides. This method works better than I initially expected—a lot of open source components separate behavior from style quite well.
Our architecture is derived from both SMACSS and Atomic Design. Ultimately, all of these sort of approaches boil down to taking an object oriented approach, both in design process and in CSS. If you’re new to the concept of OOCSS in Sass, take a read through this article to get up to speed on the basics. Also critical is an understanding of CSS specificity and inheritance.
The key idea in SMACSS is to separate your styles into categories of rules. I’ve modified the official SMACSS categories to suit our needs, but the concept of separating concerns remains. I like to visualize each category of styles as a layer. Within a layer, source order is unimportant—but source ordering of our CSS between layers helps to define style specificity and can help you avoid writing unnecessary overrides. Here’s a brief rundown of our layered structure:
I’ve been fairly loose with Bugsnag’s CSS when it comes to enforcing element naming conventions and code style. That’s partially because when I joined Bugsnag, roughly a year ago, Sass support for BEM syntax was very limited. That’s changed—now you can trivially write BEM code without much effort. Style layers in combination with Sass’s nested syntax are working well enough for us now, but that could change as our application increases in complexity and we build out our team.
This is important because many of us are working with Sprockets or another tool to concatenate partial CSS files. You may be tempted to use it to pull everything together. Don’t! Sass needs you to use [CODE]@import[/CODE] to allow things like variables and mixins to be referenced across files.
For a while I was maintaining a single manifest file [CODE]application.css.scss[/CODE] that used [CODE]@import[/CODE] to grab each and every individual SCSS file in the order I specified. It was frustrating to have to update that file every time I added a new stylesheet, so I decided to use Sass Globbing to include my style layers in one go, since they’re organized into folders. The style layer imports looks like this:
-- CODE language-plaintext --
Each import grabs all of the files located in a specified style layer directory and its subdirectories as long as the file starts with characters a-z. I force regular stylesheets to start with alphabetical characters a-z so that I can omit partial files by prefixing them with an underscore: [CODE]_partial-name.scss[/CODE]. Then I use Sass to manually import partials at specific locations in individual source files.
Once Sass has done its magic, Autoprefixer adds prefixes, then sprockets handles minification and gzip compression.
It’s really important to remember that the approach I’ve taken at Bugsnag isn’t appropriate for everyone’s needs. Also, there are things we do now that I’m advocating as great, but in a few months time, we’re probably going to find major opportunities for improvement. That’s just how it goes. Because of that, I try to remember to avoid overgeneralization at the expense of productivity. Write CSS that isn’t reusable to develop your design ideas. Eventually, opportunities to broaden the scope and reusability of your code will shake out.
What’s your take on CSS architecture? Share your thoughts with us on Twitter.