Atomic Design is all the rage; I’ve recently had the pleasure of using BEM (or CEM in Drupal 8 parlance) and Pattern Lab on Carnegie Mellon’s HCII’s site. Working through that site I landed on a front-end architecture I’m very happy with, so much so, I thought it’d be worth sharing.
CSS Architecture Guidelines
Generally I’m looking for:
- Low specificity: If specificity battles start between selectors, the code quality starts to nosedive. Having a low specificity will help maintain the integrity of a large project’s CSS for a lot longer.
- Easy-to-understand naming: Ideally it won’t take mental effort to understand what a class does, or what it should apply to. I also want it to be easy to learn for people who are new to the code base.
- Easy to pick up: New developers need to know how and where to write features, and how to work in the project. A good developer experience on a project can help prevent a mangled code base and decrease the stress level.
- Flexible & sturdy: There’s a lot of things we don’t control when we build a site. Our code needs to work with different viewports, user settings, when too much content gets added in a space, and plenty of other non-ideal circumstances.
As part of the project I got to understand the team that maintains the site, and what they were used to doing. They had a Sass tool chain with some custom libraries, so I knew it was safe in making something custom that relied on front-end developers to maintain it.
Other teams and other projects can warrant very different approaches from this one.
Low specificity is a by-product of BEM, but the other problems are a little trickier to solve. Thankfully the tools I went for when I started the project helped pave the way for a great way to work.
Class Naming and File Organization
At Lullabot we’ve been using BEM naming on any project we build the theme for. On most of our projects, our class names for a menu might break down like this:
menu- the name of the component
menu__list- Examples of elements inside of the menu
menu__link--active- Examples of variations. A variation can be made on the component, or an element.
We try to make our file organization similarly easy to follow, we generally use Sass and create a ‘partial’ per component with a SMACSS-ish folder naming convention. Our Sass folder structure might look something like this:
base/ ├ _root.scss ├ _normalize.scss └ _typography.scss component/ ├ _buttons.scss ├ _teaser.scss ├ _teaser-list.scss └ _menu.scss layout/ ├ _blog.scss ├ _article.scss └ _home.scss style.scss
On this project, I went for something a little different.
As I wrapped my head around Pattern Lab’s default component organization, I really started to fall in love with it! Components are split up into three groups: atom, molecule, and organism. The only thing that defines the three levels is ‘size’, and if the component is a parent to other components. The ‘size’ comes down to how much HTML a component is made up of, for instance:
- one to a few tags is likely to be an atom
- three to ten lines of HTML is likely a molecule
- any more is likely an organism
In the project I tried to make sure that child components were at least a level lower in particle size than their parent. Organisms can be parents of molecules and atoms, molecules can be parents of atoms, but atoms can't be parents of any component. Usually things worked out that way, but in a few instances I bumped a component up a particle size.
Pattern Lab also divides layouts into pages and templates. Templates are re-used on many web pages, pages are a specific implementation of a template. For instance, the
section-overview template is the basic layout for all section pages, and we could make an
about page, which builds on the section-overview styles.
Getting deeper into Pattern Lab, I loved how that structure informed the folder organization. To keep things consistent between the Pattern Lab template folders and my Sass folders I decided to use that naming convention with my folders.
Basically this split my components folder into three folders (atom, molecule and organism), and layouts into two (template, page). While I liked the consistency I started having a hard time figuring out which folder I put some components in, and I’m the guy who wrote it!
Fortunately this was early in the project, so I quickly reworked the class names with a prefix of the first letter of their “Pattern Lab type”.
The component classes now look like this:
The SCSS folders then followed a similar pattern:
00_base/ ├ _root.scss ├ _normalize.scss └ _typography.scss 01_atom/ └ _a-buttons.scss 02_molecule/ ├ _m-teaser.scss ├ _m-menu.scss └ _m-card.scss 03_organism/ ├ _o-teaser-list.scss └ _o-card-list.scss 04_template/ ├ _t-section.scss └ _t-article.scss 05_page/ ├ _p-blog.scss └ _p-home.scss style.scss
With those changes, a class name will inform me where to find my Sass partials, the Pattern Lab templates, and in the Pattern Lab library. Win!
Other class naming prefixes
js-. In Drupal 7 I would often remove classes from templates and accidentally break behavior, but no more!
A few examples of JS classes:
js-dropdown__toggle js-navigation-primary js-is-expanded
Another convention we have used in our projects at Lullabot is to add a
u- prefix for utility classes.
A few examples of potential utility classes:
u-clearfix u-element-invisible u-hide-text
This clearly communicates that class has a specific purpose and should not be extended. No one should write a selector like this
.t-sidebar .u-clearfix, unless they want angry co-workers.
Intersection of Particles
In past BEM projects I’ve felt uneasy about the parent/child component relationship. There are sometimes points where I feel a parent/child relationship of two components is preferred, but that’s not what BEM is good for.
When I do create a parent/child relationship, often one component’s styles will need to be higher in the cascade than the other, which means more code documentation to make sure that that ‘gotcha’ is understood, and it introduces a little fragility.
This architecture handles that issue very well.
Let’s take a look at a ‘teaser list’, it contains a title for the listing and then a list of ‘teaser’ components.
In this example the wrapper of the ‘teaser’ component has two jobs:
- define how the 'teaser’ is laid out in the ‘teaser list’ component
- define any backgrounds, borders, padding, or other interior effects that the teaser has
With the naming convention we’ve gone with, that wrapper has two classes that cover each function:
o-teaser-list__teasermakes sure the component is flowing correctly inside of its parent
m-teaserclass is responsible for the aesthetic and how the wrapper affects the contents
Using two classes will make sure the styles needed for the teaser’s layout in its parent won’t affect teasers that are in different contexts. The single letter prefix makes it even easier to to tell which class comes from the parent, and which defines the child component.
The partials are loaded in ‘particle size’, smallest to largest. I know if there’s any overlap in the styles for those two classes, the parent component’s class will win. No cascade guesswork, and no fragility introduced by the parent/child relationship.
As I mentioned before, I tried to make sure that parent components were always a larger ‘particle size’ than their children. There are a few instances where organisms were parents to other organisms, and, looking back, I wish I’d separated those organisms by adding a new particle size to the mix. Pattern Lab would have allowed me to add onto to it’s naming conventions, or even completely replace them.
I’m not selling the silver bullet to solve CSS architecture, but I am really happy with how this turned out. This setup certainly isn’t for every project or person.
Although I tend to use a few preprocessors (mainly Node Sass and PostCSS), there’s no need for any fancy preprocessors or ‘buzz word tools’, although I do find them helpful. The core of this is still something that can be written in vanilla CSS.
Leave a comment, I’d love to hear any feedback of other’s experiences trying to create these kinds of systems. What worked, what didn’t work; or specific feedback on anything I brought up here!