architecture: the complex or carefully designed structure of something
Have you ever worked on a CSS project that gradually became a hot, sticky mess? It’s difficult to keep track of what styles affect what HTML: minor changes fix one problem but create three more and can require ugly hacks, and small CSS changes can break JavaScript functionality. We’ve all been there, but these are problems that can be largely avoided by careful planning at the beginning of our projects. Let’s talk about CSS architecture.
Benefits of Thoughtful Architecture
The primary benefit of a thoughtful CSS architecture is scalability. Scalability becomes a challenge for any development project when either the scope grows or the team size increases, and CSS is no exception to this rule. The cascading and global nature of CSS makes it a powerful, but also fragile, development medium. If you’ve written CSS for any length of time, you’ve found yourself pounding your head against the desk when changing one line of CSS fixes one thing but breaks a handful of other things. Careful planning can provide the following benefits:
Fewer styling rules
Fewer styling collisions
Long-term maintainability
Faster ramp-up for new team members
Easier collaboration between team members
Smoother project handoffs
Types of CSS Rules
Jonathan Snook popularized the concept of grouping CSS rules into categories in his book Scalable and Modular Architecture for CSS (SMACSS). Structuring our rules into these well-defined categories helps us and our team better understand the purpose of each of our styles. I use seven categories for rulesets based mostly on those recommended in SMACSS, making sure every style fits neatly into one of these categories.
Base styles
Objects
Components
State
Themes
Utilities
Javascript hooks
Understanding these categories and their purpose will help give high-level meaning to the styles you write.
Base Styles
Base styles are rules created for bare elements. They are the default styles you want globally across the site. Typically, these cover things like typography, box-sizing, and elements you might want to normalize across all browsers. A common mistake when styling base elements is to be too heavy-handed and create defaults that you don’t really want. Do you really want to remove the standard bulleted list style from unordered lists globally or just in certain circumstances?
Objects
Objects are rules that focus only on structure and layout. No decorative styles allowed. The concept of object classes was popularized by Nicole Sullivan with the purpose of re-using commonly used structural or layout patterns. Look for structural patterns in your designs and create object classes that can be used across multiple components or sections of the site. By putting these styles into object classes you’ll be able to avoid redundancy, shrinking the size of your CSS. Grid systems, whether hand-rolled or borrowed from a framework, fit into the Objects category.
Components
Components are discrete, self-contained pieces of UI. They are the bread and butter of atomic design and will make up the bulk of your styling. A component can be as small as a button or as large as a carousel. The key to creating robust components is to make them independent from any other parts of the page and self-contained. You should be able to drop a component anywhere on any page and it will maintain its structure and design.
State
State classes are helpers that modify the state of a component. Think of accordions that are open or collapsed, links that are active or inactive, or elements that are hidden or visible. It’s common to add/remove state classes with JavaScript. Rather than manipulating styles with JavaScript, you can just update a state class and allow the stylesheet to determine what each state looks like.
Themes
Theme classes simply alter a component to use unique colors, fonts, or other decorations. Theme classes can be used to modify an entire page or just a single component. Themes aren’t used on every project but can be useful when you need them.
<blockquote class="c-pullquote t-light">
<p>A great quote from someone special.</p>
</blockquote>
Utilities
Utility classes are single-purpose helpers that apply one specific styling rule. They can be used to tweak spacing, increase font size, center text, add clear fixes, hide things, etc. Utilities can help you with minor layout adjustments like adding space between components or clearing floats. They can also be used to make minor changes to existing components without the need to create a new component variant.
.u-sp {
margin-bottom: 1em !important;
}
.u-clearfix:after {
content: " ";
display: block; clear: both; visibility: hidden;
height: 0; font-size: 0;
}
.u-txt-center {
text-align: center !important;
}
.u-txt-larger {
font-size: 130% !important;
}
<div class="promo u-sp"></div>
<div class="promo u-sp"></div>
<div class="promo"></div>
Javascript Hooks
Whenever possible, decouple any dependencies between your JavaScript and styling. Using class names that are used for both styling and DOM selection with JavaScript can cause issues later down the road when the CSS is refactored and the JavaScript dependency is not clearly evident. Instead, use classes that are completely dedicated as JavaScript hooks.
<button class="btn btn--buy js-buy-now">
Naming Classes
When naming your classes, make sure that your names are long enough to discern (.pullquote not .pq), but no longer than they need to be (.nav not .navigation). Readability of class names can make a significant impact in helping team members both now and in the future understand the logic behind your presentation.
Creating descriptive, meaningful names is one of the toughest problems in writing CSS, but also very helpful if done thoughtfully. Space doesn’t allow for an in-depth treatment of naming things, but see our very popular post, Naming CSS Stuff is Really Hard, by Ethan Muller for more details.
BEM Naming Convention
A very popular and super-helpful convention for naming CSS components is BEM (Block Element Modifier), developed by the Yandex, the popular Russian search engine. The naming convention is very simple:
[BLOCK]__[ELEMENT]--[MODIFIER]
You might struggle with using such verbose class names, but the value added by using BEM on your projects will quickly supercede such concerns. Here is an example component that uses BEM class names.
<div class="alert alert--warning">
<h1 class="alert__title">
<span class="alert__icon"></span>
Alert Title</h1>
<p class="alert__description">The password you entered is invalid.</p>
</div>
BEM naming provides three primary benefits to your project:
Readability: Using clearly described class names for most of your elements will make it much easier for someone else reading through your HTML or CSS files.
Self-description: Using hierarchical names for your classes makes it very clear which elements belong to which base components.
Specificity: It may seem excessive to add a class to every element in your component but, by doing this, you can keep the specificity of each of your selectors low, making overrides much more straight-forward.
Namespacing
Another best practice when naming your classes is to use prefixes for namespacing the class categories we described previously. These prefixes add a couple characters to your names, but the value of being able to immediately identify the purpose of each class name you see in your HTML or stylesheets is invaluable. Here are the namespaces I use:
Objects:
.o-
Components:
.c-
State:
.is-
OR.has-
Theme:
.t-
Utilities:
.u-
Javascript hooks:
.js-
<footer class="c-footer">
<div class="o-container-wide">
<a class="c-footer__logo" href="/">The Assist</a>
<div class="c-social c-social--follow">
<div class="c-social__label u-txt-center">Join the conversation</div>
<ul class="c-social__list">
<li class="c-social__item"></li>
<li class="c-social__item is-active"></li>
<li class="c-social__item"></li>
</ul>
</div>
<p class="c-footer__credit"></p>
</div>
</footer>
For more information on the value of namespacing, check out Harry Roberts’s post on the subject.
Code Style
Like any code, it’s important that your CSS project make use of a consistent coding style. This includes guidelines around white-space, indentation, naming conventions, comments, etc. You can find some reasonable guidelines from Google, Github or Nicolas Gallagher. Use theirs or create your own similar set of guidelines.
Code Organization
For optimal code organization you should be using either a pre-processing tool (Sass, Less, Stylus) or a post-processing tool (PostCSS) to compile your code. The advantages are many, including features such as variables, functions, mixins, imports, and nesting. These features will enable you to implement a more organized architecture than what you can do with CSS alone.
Using imports, you can divide your styles into meaningful files.
@import "variables";
@import "mixins";
@import "normalize";
@import "typography";
@import "headings";
@import "headings";
@import "layout";
@import "carousel";
Use variables when any value needs to be used more than once. Prefix your variable names to help identify their purpose and also to make code-completion more useful.
// Colors
$c-warning: Red;
$c-primary: Blue;
$c-background: White;
Some variables are global in nature and should be stored in dedicated variables files, but other variables are only used within a single component and should be defined within the file that uses them. In Sass, variables can be contained to a localized scope within a nested ruleset structure.
.alert {
$background-color: Red;
$foreground-color: Cream;
background-color: $background-color;
color: $foreground-color;
}
Source Order
Because of the nature of the CSS cascade, the order of your styles matter. If you are not purposeful about how you order your imports, you will find yourself constantly fighting against the cascade.
Recently, Harry Roberts published a sensible method for ordering your styles that he calls ITCSS (Inverted Triangle CSS), with a goal of preventing namespace collisions, specificity issues, leaky styles, and inadvertent regressions (see his in-depth slides). The concept is very simple: order your settings and rules starting with those with the broadest reach and lowest specificity and end with those that have the most narrow reach and highest specificity. This means that your variable definitions and bare element rules will always be at the beginning, while your utility classes and IE hacks will go at the end.
Harry defines seven groups that our files should fit into and sorted in the following order:
Settings: Variables and other settings
Tools: Custom functions and mixins
Generic: Font-face, box-sizing, normalize, etc.
Elements: Bare element defaults like headings and links
Objects: Layout and structure classes
Components: Individual components
Trumps: Utilities and other rules meant to be a final trump over everything else
@import "settings.global";
@import "settings.colors";
@import "tools.functions";
@import "tools.mixins";
@import "generic.box-sizing";
@import "generic.normalize";
@import "elements.headings";
@import "elements.links";
@import "objects.wrappers";
@import "objects.grid";
@import "components.nav";
@import "components.buttons";
@import "components.promos";
@import "trumps.utilities";
@import "trumps.ie8";
Digging Deeper
This write-up is just an introduction to a vast topic that goes deep and wide, but hopefully it inspires you to think more thoughtfully about how your CSS projects are structured and designed. If you want to dig into this topic further, follow the many links embedded into this post, and check out the following resources and thought leaders in this space.
Harry Roberts - One of the most prolific thought leaders in this area currently. Follow him on Twitter, subscribe to his blog, and read through his CSS Guidelines document.
Jonathan Snook - Popularized the idea of CSS architecture with his paper and online book, Scalable and Modular Architecture for CSS.
Nicole Sullivan - Introduced the concept of Object Oriented CSS currently documented in a Github wiki.
This post is based on my recent presentation on the same topic. Checkout the slides for the presentation or watch the video below.
Sparkbox’s Development Capabilities Assessment
Struggle to deliver quality software sustainably for the business? Give your development organization research-backed direction on improving practices. Simply answer a few questions to generate a customized, confidential report addressing your challenges.