CSS Architecture: First steps

CSS was designed to be a very simple and intuitive language. Its basic purpose is to define selectors to target HTML elements and apply attributes to them. Easy to grasp and start applying. When working on large and complex projects, though, some CSS features might be a little trickier to deal with.

Its cascading feature, for example, allows properties to be overridden by other selectors according to its order on the sheet or by selector specificity. Also, elements inherit properties from their parents on the DOM. Without proper organization, this can quickly get out of hand and messy. That’s where an architecture comes to help.

What is a CSS Architecture?

A CSS architecture brings reasoning to CSS authoring. Imagine it as a set of guidelines and best practices to help developers write code that’s maintainable, flexible to scale and more reusable. We achieve that by applying a modular approach, promoting organization and conveying meaning to our codebase. We’ll see how to apply these principles with 3 basic steps.

Modularity is a core concept. Not only on a code level, but also on a design one. Digital products have evolved a lot in the past years, due to the rise of new devices and the responsive web. Complexity has changed. We, as developers and designers, have to support this new scenario. Designing pages is no longer the best approach; we should build design systems. Page-based CSS won’t suit our needs on building flexible and scalable products. Working with reusable components is the way to go.

On a CSS architecture, modularity can be applied on several levels. The three basic steps to start structuring your CSS that way are by:

  1. breaking the code into smaller chunks and separating them by scope;
  2. coding components in a independent and encapsulated manner;
  3. naming CSS selectors according to their purpose and relationship with each other.

Getting started

1. Separate and categorize your code

To keep the architecture modular, it’s important to break the code into smaller parts. Multiple files make the code easier to read, navigate and track. To do that, a CSS preprocessor – such as Sass, LESS or Stylus – or a post-processor – such as PostCSS – is the way to go. Preprocessors enhance the capabilities of CSS authoring, introducing new features such as variables, mixins and much more. To work with separate files, your code will be divided into partials and imported on a main.scss file, that will compile everything into a single .css file.

main-scss
Example of a main.scss file. (Spoiler alert: it’s already categorized :P)

Now it’s time to categorize each code block according to its scope on the project. One of the first methodologies out there to introduce selector categorization was SMACSS. The layers proposed by SMACSS are Base, Layout, Module, State and Theme. ITCSS expanded this concept, introducing other layers, such as Settings and Trumps. MVCSS has a similar approach, but with different naming conventions.

ITCSS layer convention. https://speakerdeck.com/dafed/managing-css-projects-with-itcss?slide=49
ITCSS layer convention.
https://speakerdeck.com/dafed/managing-css-projects-with-itcss?slide=49

You don’t need to follow these methodologies strictly. Learn the examples and figure what better fits your project. In my opinion, separating your architecture into Settings, Base, Layout and Components is a good start. Settings is where your configuration files, such as variables (colors, sizing, typography definitions…) and helpers (functions, mixins…) will be stored. The Base layer will contain your reset and unclassed HTML rules – the way a default <a> or <input> should look like, for example. Layout stores the structural classes that hold components on the page – grids and containers, for example. And lastly, Components: the core of CSS architectures and design systems.

2. Define your Components

I consider components the core of a CSS architecture because that’s where most of the code will live. They are the reusable visual patterns that compose a user interface – buttons, accordions or modals, for example.

A button component.
A button component.

Each component should be encapsulated on its own file. For consistency reasons, it’s a good practice to use the same name for the file and main selector.

A button.scss file.
A button.scss file.

Some components aren’t as straightforward as a button. Some are more complex, composed of inner elements. Take this blog post component, for example. It has a thumbnail, a title, an excerpt and a link.

A blog post component
A blog post component

Sometimes, when defining components, you’ll be asking yourself what should be a standalone component or part of a larger block. To answer that, search your product or website for repeating patterns. If it is used on different contexts, abstract it to a standalone component. On the blog post example, the .link element is a separate component, because it is also used on other parts of the website. The other elements are specific to the blog post. Let’s see how to handle this relationship with selector names.

3. Apply a naming convention

Remember: we want our architecture to convey meaning and promote organization. Using a class naming convention in our components will help us achieve that. A pattern such as BEM (Block, Element, Modifier) will clarify the intention and relationship between a component (the Block) and its elements, as well as reinforce predictability when compared with other components. The idea behind BEM is to name things according to this structure:

  • Block: the component itself;
  • Elements: the inner parts of the components (descendents on the DOM);
  • Modifiers: variations of the block or element.

Using the blog post example:

Block

.blog-post { … }

The blog post itself.

Element

.blog-post__excerpt { … }

The excerpt inside the blog post. To denote the relation between the Element and the Block, BEM uses two underscores.

Modifier

.blog-post--small { … }

A variation of the blog post that would make it smaller. To denote the relation between the Modifiers and the Block, BEM uses two dashes.

On a organizational level, this approach promotes consistency and predictability across the Component layer. On a technical level, it helps decreasing selector specificity, increasing selector efficiency and decoupling our CSS from DOM structure (thus, promoting reusability). Take this other example:

 

section.container div.grid div p { … }

 

On this scenario, the browser has to check for all the conditions to apply the selector:

Is there a <p> inside a <div> inside another <div> with a class of .grid inside a <section> with a class of .container? If so, style this.

Complex, right? And a developer that stumble upon that selector might ask himself:

What’s the purpose of this selector on a broader scope? How does it relate to other selectors?

The BEM class-based approach, on the other hand, is much more precise and meaningful. The browser has only to check for the .blog-post__excerpt class on the DOM, which is more performatic. It can be reused, since it isn’t tightly coupled with DOM structure. And the developer can assume that it is part of a .blog-post component.

Wrapping up

These tips are just a starting point, a basic foundation and first steps for structuring CSS projects. For further reading on the subject, I recommend visiting this list that I’ve compiled with some awesome thoughts and methodologies from great developers that inspired this article. There are lots of different approaches out there (lots, really). Feel free to contribute to it with an issue or pull request!

About the author.

Thiago Victorino
Thiago Victorino

A digital product designer that enjoys writing awesome UI code and works to shorten the gaps between design and engineering on product development.