CSS Architecture Archeology

In web development, one of the most crucial aspects of creating efficient and maintainable CSS is the ability to reduce code duplication and establish a reliable system of reuse. This approach not only streamlines the development process but also enhances the overall performance and scalability of web projects. Thoughtful CSS architecture strategies can create a more organized and flexible codebase that allows for easier maintenance and faster iterations.

In this post, I reflect on some of the CSS architectures that have influenced my approach to structuring and reasoning about CSS, exploring their strengths, limitations, and impact on my web design practices.

Starting with SMACSS

Back in 2011 Jonathan Snook wrote a book about Scalable and Modular Architecture for CSS (SMACCS). I was immediately drawn to this concept, and my teams ended up implementing it across all of our projects at the company I was at, it proved a means to think about the architecture of CSS in a way that worked with, and built on top of the cascade.

Ethan’s industry-changing Responsive Web Design had only been published a year or so prior, and SMACSS provided a lens to look at web pages as a structural layout that gets populated by modules (we call these components today). This approach to organizing and thinking about front-end code as reusable modules was a major step towards creating more robust reusable systems.

Moving to and Using BEM

While I can’t pinpoint exactly when I was introduced to or started using BEM, I have been using it for the better part of a decade. BEM (Block, Element, Modifier) is a naming convention for CSS that quickly saw adoption among many teams in the 2010s because it provided a meaningful and robust means to think about the relationship between elements, and how we named them.

The core idea behind BEM was the relationship between the three parts of it’s name, the block, element, and modifier. A block is a standalone entity that is meaningful on it’s own, some examples are header, navigation, or menu. An element differs from a block in that it does not have any meaning by itself, and is directly related to the block it is a part of, a menu item for example. Finally a modifier is a flag on a block or element that indicated how it is different than the base configuration or style.

BEM allowed teams to quickly and easily understand and reason about the architecture of their components, you’d be able to see at a glance how and when sections of code related to each other, and what was unique about them. A block would simply be a class name, where an element would need to follow the block class with a double underscore indicating it’s relation to the block, something like card and card__description.

Common issues with BEM

While BEM was undoubtedly helpful it also suffered from a number of issues that would inevitably come up when working on larger components and codebases.

The most common question or concern that would come up was that of the “grandchild” selector. In BEM, every element is a “child” of a block, but there is no direction or indication on what to do if an element has “children”. A frequent example of this issue is navigation blocks.

<nav class="navigation">
  <ul class="navigation__list">
    <li>
      <a href="">One</a>
    </li>
    <li>
      <a href="">two</a>
    </li>
    <li>
      <a href="">three</a>
    </li>
  </ul>
</nav>

In this example we have our navigation block, and a navigation__list element, but how are we supposed to name the list items and links? The proposal that typically comes up first is the “double element”, navigation__list__item and navigation__list__link. According to BEM standards, every element should be directly related to it’s block, so the “correct” way to name these would be navigation__item and navigation__link, this can be confusing for individuals when working on large components that may have many descendants or complex relationships.

The second most commonly discussed issue is the fact that BEM does not generally align with or allow for generic or utility classes. In BEM any time you’re adjusting or modifying something you should be using a --modifier class. This strict pattern often results in significant code duplication for common patterns like --hidden or --active, where in other systems you might simply have a generic is-hidden state class that is reused across multiple components.

Enter CUBE CSS

In the last year or two I became aware of Andy Bell’s CUBE CSS architecture. CUBE (Composition, Utility, Block, Exception) CSS is an architecture that I feel builds on some of the best parts of both SMACSS and BEM, while solving issues that both had. CUBE is; as Andy puts it “an extension of CSS, it builds on the foundational function of the cascade and then enhances it with additional functionality.

The Composition portion functions very similarly to the layout function of SMACSS in that it defines the macro “composition” of elements. The biggest difference is that in CUBE a composition is not tied to a page layout alone, it could be used to define a page, section, or even single component’s compositional properties.

The Block in CUBE is equivalent to it’s BEM counterpart, but without the strict syntax rules. A block is used to identify and style a group of elements that “only apply in that context.” Unlike BEM however, because most of the “structural” work has been done in the composition layer, block styles are much lighter and specific to the unique context of the block.

Utility styles are built right into the name of the architecture, so they’ve got first-class treatment and support. In CUBE utility styles are very small and specific, doing only one thing: bg-primary vs bg-secondary defining only the background of an element, or sr-only applying styles that visually hide an element, but leave it available to assistive technology.

I’ve only used CUBE-style architecture on a few side projects, but my experience with it has been exceptional. This site is built using CUBE ideas and the composition classes alone feel like a superpower when coming from a strict BEM environment.

The Tailwind Sized Elephant

I’d be remiss if I didn’t talk about tailwind in a post about CSS architecture. For me personally tailwind isn’t the vibe, I know plenty of developers who enjoy working with it and think it “solved CSS,” but the complete dismissal of the cascade and excessive markup don’t align with my own personal feelings of front-end code. I’d much rather have some semantic meaning when looking at my code than an alphabet soup of pure utility classes at every level.

Thoughts Moving Forward

In today’s modern component-based development ecosystem something along the lines of CUBE’s architecture feels like it strikes the right balance of meaningful code and developer freedom. Within a design system we can expose these sorts of compositional freedoms through “structural” components like a stack, box, or card. This type of structural composition component can be lightly prescriptive in what they do, and allow development teams freedom to place whatever content is required for the interface inside of them. Pairing these with existing block style components like button or badge provides teams with the ability to create a multitude of layouts and interfaces without the need to override or duplicate code for one-offs.

Adam Sedwick

I work on Design systems and Advocate for Accessibility on the web.

Tennessee

Blogging

Design Systems

Design Tokens

Web Accessibility

Web Design

Web Development

Open Web Standards