How to Scale and Maintain Legacy CSS with Sass and SMACSS

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

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.

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.

.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.

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:

modules/_my_module.sass

12345

.my-modulecolor:chartreuse@if$ie7position:relativezoom:1

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 @extending 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.