The Wide World of CSS Conventions

OOCSS, Atomic, BEM, SMACSS, SUIT, AMCSS, homebrew methodologies... What's the deal?

Illustration of a planet with many moons orbiting it.

I've been exhausted recently and tying up some loose ends on some other projects, but I felt like creating a (little? hopefully?) article on a few different conventions that can be used when dealing with CSS. Why? Well, it probably has something to do with those projects being at the stage where I clean up the stylesheets...

My typical method of dealing with styles when I start a brand new project is to be very liberal in my use of CSS class selectors and then choose a better way of dealing with the scripting when the site or app's structure is fully developed. It starts rather ugly and unwieldy, with large monolithic files and many class names, but eventually begins to shrink once I have begun the housekeeping process. Ultimately, class names and style applications make sense, and I can sort out the nonsense from the "must-haves."

The truth about CSS is that there is no one way that you should work with it outside of its technical implementation. There are some general best practices, and there are some things you should probably never do (like use important! outside of very niche circumstances), but as long as your method is working for you, keep going.

However, a problem arises when it's not just you working with styles—the problem of making it make sense to others.

CSS is an inherently janky scripting language developed over many decades, with parts bolted and taped on to add functionality while not breaking the entire internet. Browser vendors have often not wanted to play perfectly well with each other. Its troublesome nature lends itself to two possibilities:

  1. Creating one and only one possible "best practices" methodology that everyone everywhere must adhere to.
  2. Creating many possible methodologies to capture individual and organizational workflows, often based on team specialties and experience level or site and app structure.

Number one simply never happened. Outside of a vague direction of "keep design and functionality separate," any attempt at creating a worldwide "best practice" when working with CSS was impossible. Even keeping design and functionality separate didn't make it through the front-end gauntlet unscathed. The second possibility was what inevitably grew around CSS since its inception in the mid-90s.

The consequence, of course, is a world of possible conventions. So many that only the most experienced front-end developers and designers have a strong level of experience with all of them. This isn't a negative—it's just a reality. There are only so many hours in the day, so many organizations people get the chance to work for, and so many opportunities to work on certain types of products implemented with specific design and architectural choices.

And there is no correct answer to "What CSS convention should I use?" It isn't a question worth taking seriously in a universally-applicable manner. It will always be a personal or organizational choice based on some amount of subjective preference around what works best for the developer or the team.

Some of the "conventions" outlined below are more like component styling systems or preprocessors than pure CSS methodologies. I'll note the conventions that would require installing a dependency. If you're like me, you try to avoid dependencies, and you might not want to deal with the potential headaches they can cause in the long run.

So which methodology should you use? Who knows! That's up to you or your organization. There isn't a "right" answer. With that in mind, let's look at some of the most common CSS methodologies out there. Maybe we'll learn about something new.


BEM

Let's start with a classic. BEM stands for block__element--modifier, precisely how this CSS convention works. It focuses on styling components and naming your CSS classes around them. I'm partial to this one for its straightforward nature. Consider the following simple HTML that could be for something like a card in a feed:

<article>
    <a href="/link/url.html>
        <h1>Card Title Text</h1>
        <img src="/img/url.png" alt="An image."/>
    </a>
</article>

The block in this HTML is the "component"—the card itself. This is the semantic article element and would be given a CSS class name:

.card {
    /* Styles */
}

The card component also consists of multiple elements, such as a, h1, and img. Using the BEM convention, their CSS class names could be:

.card__link {
    /* Styles */
}
.card__title {
    /* Styles */
}
.card__image {
    /* Styles */
}

Note the two underscores between the component and the element in these class names.

And finally, it may be helpful to apply modifiers to a component or an element within it. A common theme adjustment that a site or app may have is "dark mode," which would use the following names for the CSS classes:

.card--dark {
    /* Styles */
}
.card__link--dark {
    /* Styles */
}
.card__title--dark {
    /* Styles */
}
.card__image--dark {
    /* Styles */
}

So there it is, the block__element--modifier convention applied to all levels of a component's styles.

While BEM is a bit wordy and class-selector-heavy, you can see how it creates a nice logical flow with its naming system. The relationship between components, elements, and modifiers is evident in the name of the CSS class being used. Looking at the HTML shows any developer the relationships automatically:

<article class="card">
    <a class="card__link" href="/link/url.html>
        <h1 class="card__title">Card Title Text</h1>
        <img class="card__image" src="/img/url.png" alt="An image."/>
    </a>
</article>

There is more to this convention than simply naming things correctly, so read up about it.

OOCSS

If you're a horrible person like myself, you end up with a lot of duplicated CSS when playing around with a new idea. Consider some header and footer styles in a massive monolithic styles.css file in an assets folder somewhere:

.site-header {
    margin: 0 auto;
    width: 90vmin;
    height: 300px;
    background-color: #000;
}
.site-header-nav-item {
    font-family: Arial, sans-serif;
    font-size: 2.2rem;
    color: #EEE;
    padding: 1rem;
    margin: 0 2rem;
}

/* Another 200 lines of ugly CSS */
 
.site-footer {
    margin: 0 auto;
    width: 90vmin;
    height: 300px;
    background-color: #000;
}
.site-footer-nav-item {
    font-family: Arial, sans-serif;
    font-size: 2.2rem;
    color: #CCC;
    padding: 1rem;
    margin: 0 2rem;
}

This example has so many duplicated values. The problem is obvious—maintaining and modifying this stylesheet will be a nightmare. If the margin and padding of the nav-items needed to be adjusted, we would have to go back and forth between them. The same applies to the header and footer and their width and height values. Now spread that across an entire project, and then tear your hair out when someone needs juuuust a little more padding around every (insert common element here).

Conceptually, these commonalities can be abstracted out into their own classes. This is the idea behind Object-Oriented CSS (OOCSS):

/* Structural values determine position and spacing */

.site-width {
    margin: 0 auto;
    width: 90vmin;
}
.site-framing {
    height: 300px;
}
.nav-item-spacing {
    padding: 1rem;
    margin: 0 2rem;
}

/* Skin determines things like color, font, and so on that most closely
   resembles what we think of in terms of "theme" */
   
.frame-color {
    background-color: #000;
}
.nav-item-font {
    font-family: Arial, sans-serif;
    font-size: 2.2rem;
}
.header-nav-item {
    color: #EEE;
}
.footer-nav-item {
    color: #CCC;
}

Technically, we now have more classes in this (quite limited) example. This will have the side effect of adding a little bloat to HTML class attributes, as there won't be a one-to-one mapping anymore. But what is lost in that area is made up for in streamlining site-wide changes via CSS. Is site-width's width of 90vmin too wide? Changing one line in a stylesheet now fixes the problem, completely removing the need for hunting through a project and pressing ctrl + f, and continually referencing every HTML element to see where the width value may have been set. Responsive media queries typically would require far fewer lines, as well.

This is just the conceptual idea of OOCSS. Its core tenets are "separating structure and skin" and "separating container and content." The GitHub wiki for the OOCSS project has a much more rigorous description. There are some suggestions I disagree with contained within the documentation (the use of camelCase, for instance). Still, if you're looking for a logical way of managing CSS for a large project, OOCSS is an excellent direction to move in.

Atomic CSS

Atomic CSS (ACSS) is a way of working with CSS that generates single-purpose CSS classes:

.oh {
    overflow: hidden;
}
.w70vm {
    width: 70vmin;
}
.cfff {
    color: #fff;
}

Yes, I know. This looks counterproductive to the extreme.

While it is drastically different than the usual method of working with CSS, it does have its benefits. It's straightforward to programmatically inject styles with (insert framework or JavaScript solution here). It also reduces redundancy—there will be no repeated CSS in classes when each class stands for a specific value.

The downfall is the lack of easy use of media queries, a massive loss for the responsive design crowd. I make heavy use of media queries, so I'm not too fond of this methodology. My brain thinks in media queries—I try to design sites based on media queries so that the entire site flows between potential device widths. My mind simply recoils at Atomic.

Luckily for those who want to implement ACSS, there are several preprocessors and other tools out there that will allow you to write CSS in an Atomic manner.

Still, I (personally, respectfully) disagree with this Atomic CSS approach. To quote someone else,

CSS architectural approaches are the diet pill of front-end developers.

However, some dogma is often appreciated in large projects with multiple teams jumping in and out of the same codebases. And tool-assisted programming is a force multiplier in those situations, which also leads nicely into...

SUIT CSS

SUIT is a methodology with a host of tools available for implementation. I'm once again not a fan of the default naming convention. PascalCase shouldn't have a place in CSS, but that's never stopped anyone from using it.

SUIT has its upsides, however, and I can get over PascalCase in my CSS if necessary. It most certainly takes advantage of the strength of preprocessors and variable creation. Its component-focused design lends itself to use in many popular front-end frameworks out there in the wild and has been adopted by some big names online.

You might notice a slight similarity to BEM in the feel of SUIT's class names, especially when it comes to themes:

.ProductCard {
    /* Styles */
}
.ProductCard-link {
    /* Styles */
}
.ProductCard-title {
    /* Styles */
}
.ProductCard-image {
    /* Styles */
}
.ProductCard--light {
    /* Styles */
}
.ProductCard--dark {
    /* Styles */
}

It has a utilities package available that generates low-level style modification. One thing used in the utilities implementation is !important, which is bad practice in most other contexts.

See? CSS' rules were meant to be broken, both figuratively and literally.

SMACSS

The inspiration for SMACSS (Scalable and Modular Architecture for CSS) came from many different design principles and conventions, as listed by the author on the "project's" site:

Nicole Sullivan's Object Oriented CSS, Jina's presentations on CSS Workflow, Natalie Downe's talk on Practical, maintainable CSS, and, lastly, Jeremy Keith's Pattern Primer

According to Jonathan, SMACSS is "more style guide than rigid framework." It focuses on five categories of rules that should be broken up into their own files or sections:

  1. Base
  2. Layout
  3. Module
  4. State
  5. Theme

I would write a detailed explanation, but SMACSS is a whole thing. Layout rules work with major and minor components, modules are even more specific elements on a site or app, state consists of styles that are modified by, well... state... and themes are optional (once again, the obvious example is a light/dark mode toggle.)

SMACSS does feel less like a "convention" and more like an architectural methodology. A free e-book covers it in detail while not being hundreds of pages long. Please give it a read if you think this system might help bring sanity to the potential chaos of stylesheets.


It's your choice

Of course, the CSS methodologies and conventions you use are up to you. These are just a tiny sampling of what could be done with names, file structure and project architecture, tools and processors, and selector and style use and reuse strategies. Countless homebrew solutions work just as well given their contexts... and many more perform poorly and cause headaches and ulcers and burnout.

When it comes to the well-known conventions, I am partial to BEM and OOCSS (I often use a homebrewed modification of OOCSS in the smaller projects I typically work on). Neither requires any dependency to implement, and they're so widely used and "common sense" that a front-end developer can immediately dive into the stylesheets and get to work.

I love the idea of Atomic CSS, but I find it annoying when all is said and done. The extreme atomization present in it screams, "Why even make CSS files at all? Write inline styles and be done with it!"

Outside of the conventions described above, I am also interested in ECSS, which I have never worked with directly and have little knowledge of, so I didn't include it in the article. Another notable mention I didn't get to is AMCSS, which is meant to circumvent classes as a method of styling pages. It's built for component-forward design, so check it out if that sounds interesting, especially if you use a framework like React.

There is no shortage of potential conventions that can be adopted when managing your CSS, including the homebrewed version that only you can parse. At the end of the day, as long as your system works for you and your team, it's a good one. After all, the solutions listed above had to start somewhere.