We’ve been big fans of SMACSS for a long time, however a pure SMACSS approach works best with plain old CSS on a brand new project. On our marketplace sites, we write style sheets in Sass and have several years’ worth of legacy CSS to deal with.
We’ve recently been refining the way we do CSS to make it easier for our growing team to maintain our style sheets without throwing away our existing front end & starting from scratch.
What we’ve ended up with is an approach loosely based on SMACSS that still solves the problems originally tackled by SMACSS & OOCSS, but with a few modifications and some ideas cherrypicked from places like BEM and Toadstool.
Note: You’ll need to be somewhat familiar with SMACSS for the rest of this post to make any sense. Here’s an overview.
Our take on SMACSS
SMACSS defines five categories of styles:
- Base
- Layout
- Module
- State
- Theme
Our approach looks more like this:
- Base
Layout- Module
- State
Theme
Base
In newer projects our base styles are just Normalize plus some basic default element styles (colors, typography, margins & padding).
Unfortunately, the marketplace CSS comes from a time when aggressive CSS resets were cool, as well as having some unfortunate default typography styles that we pretty much always override. These styles are difficult to change without affecting all the other CSS that has been built on top of them over the years.
Even with those drawbacks, we can still treat our base styles just like SMACSS base styles.
Layout
In our approach everything that is not a base style or a global state class is a module.
SMACSS draws a distinction between major layout components (header, sidebar, grid, etc) and everything else. We’ve found this distinction unnecessary for a couple of reasons:
- Modules often “lay out” their child components in the same way that major components are laid out on the page.
- Even if we’re 100% certain a component will never be reused, there is no benefit in treating it any differently to reusable components.
The line between layouts and modules is too fuzzy for it to be worth keeping layouts around as a special category.
Module
Modules are standalone, reusable components that have no knowledge of their parent container. Their only dependencies are the app’s base styles. We can safely delete a module when it’s no longer needed without causing changes elsewhere in our CSS.
BEM double underscore syntax is used to scope child components to a module, and we use CSS child selectors liberally to minimise the depth of applicability.
BEM double hyphen syntax is used as a modifier to indicate subclasses or, when
used in combination with the is-
keyword, module-specific state classes.
Since setting a module’s width, position or margins would require knowledge of the context it appears in, our modules are always either full-width block elements or inline elements.
Here’s a simple example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
When I first started writing modules like this I’d often end up with huge
modules with complex class names like
.my-module__child-component__grandchild-component--modifier
.
But aside from position and dimension properties, most child components can be extracted out into their own standalone modules. So if you leave the positioning up to the parent, we end up with 3 smaller, standalone modules.
1 2 3 4 5 |
|
1 2 3 4 5 6 |
|
1 2 3 4 5 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
.grandchild-component
and .child-component
are now independent of their
parent containers. It’s up to a module to position containers for its
children. You do end up with a tiny bit more structural HTML, but with the
benefit that nested UI components are now completely decoupled from each
other.
State
Module-specific state classes are defined in the same file as the module
itself (see .my-module--is-active
above) but we keep global state classes
separate, e.g. .is-hidden
.
Theme
We do “theme” each of the eight marketplaces (e.g. different color schemes on ThemeForest and GraphicRiver), but we achieve this by setting variables for site-specific styles in a config file.
Some Sass magic to bring it all together
Configuration
_config.sass
is the very first file included in our main application.sass
.
In it, we assign to global variables all the common values we’ll use. This
includes colors, font sizes & families, responsive breakpoints and more.
We also include a marketplace-specific config file to set variables for each marketplace’s color scheme.
Mixins
All of our mixins are kept in their own files in a mixins
directory and are
available globally. We also import a few vendor mixin libraries, like
Compass which we use for spriting and vendor
prefixing.
Grid
Even our grid framework is just a module.
We’re steering clear of using grid classes like .span-5
in HTML and instead
using Susy to keep column-based widths in CSS
alongside the modules they belong to.
1 2 3 4 5 6 7 8 |
|
We wrap everything in a .grid
module that’s just a Susy grid
container.
Internet Explorer
We’re still supporting IE 7 & 8. We used to use the
HTML5 Boilerplate method
of targeting those browsers, but that meant we were sending lots of styles
scoped to .lt-ie8
down to other browsers that would never use them.
Now we’re using this trick to
generate standalone application-ie8.css
and application-ie7.css
style sheets for those browsers.
IE-specific styles (like everything else) live right inside the module they belong to:
1 2 3 4 5 |
|
Good browsers are served a nice clean application.css
without any IE junk,
while old IE users get their own special version instead.
What we’re not doing
We’re not aiming for readable CSS output. When you write modules like this you can look at the class name and go straight to the correct Sass file instead of digging around in DevTools trying to figure out where a certain style is coming from. Also, Source Maps.
We’re not aiming to remove every last bit of duplication from our compiled
CSS. We want to make development as easy as possible without impacting
performance. So far including mixins in modules (and thus adding some
duplicate styles) instead of @extend
ing everything in sight hasn’t hurt us
in terms of raw file size (gzip chews up most of the duplication - we’re
sending about 39kb of CSS over the wire).
Conclusion
We started a few months ago by adding a single module file to our somewhat
dirty style sheets
directory. As we add new features & convert existing ones,
our modules
directory is steadily outgrowing what’s left of our old CSS.
The main application.sass
file for ThemeForest now looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
We were successfully able to modernise our CSS architecture without throwing out our existing front end and starting from scratch.
Further reading
- style is a reference starter project for this approach. Try it out and let us know what you think!
- If you haven’t read SMACSS, most of it is free to read on smacss.com.
- Harry Roberts has a whole series of great posts on scalable CSS. Start with this one on BEM syntax and work your way through the archives.
- Nicolas Gallagher has also done a ton of work in this area. About HTML Semantics & Front End Architecture is a good overview of some of the thoughts that led to our approach.