CSS Architecture and the Three Pillars of Maintainable CSS

Elements of CSS Architecture

If you have ever inherited bad CSS with the obligation of maintaining it, you might have felt some empathy towards others who would maintain your code in the future. In extreme cases, developers have no other choice, but to completely rewrite the CSS of an application. These extreme cases occur when each patch introduces multiple unwanted side-effects. Once you get to this point, your stylesheets become unmaintainable.

You can only avoid giving your future self a checkmate by making architecturally solid decisions now. This is why it is important to learn the ins and outs of building a maintainable CSS architecture in practice.

If you don’t want to be the person passing on bad code, you may wonder how you can create maintainable CSS from scratch. Where would you start? Let’s look at the elements of CSS architecture that are worth considering when it comes to building your perfect project.

Three Pillars of Maintainable CSS

There are three concepts worth considering when designing the CSS architecture of a software system. These concepts are so fundamental we can consider them like pillars holding up the structure of a building. We need all three pillars to make our CSS endure the test of time and doesn’t collapse into an unmaintainable mess.

The first pillar defines the building blocks of your CSS architecture. These building blocks consist of a wide variety of solutions and tools, such as using Sass, writing efficient CSS selectors, the block-element-modifier (BEM) syntax, using classes instead of ID attributes, and using relative units where appropriate.

Although this perspective gives you measurable improvements in your CSS code quality, we need a higher level of organization to make our efforts systematic. Therefore, we need a second pillar that focuses on the orchestration of building blocks to establish solid, maintainable, hierarchical CSS. Think of this layer as the skeleton of your CSS architecture. If you are interested in two ready-made CSS architectures, research a bit more about ITCSS and SMACSS.

Unfortunately, neither the building blocks, nor a methodical usage of a framework or a CSS architecture give you the answer to writing rock solid, maintainable CSS. Our code becomes solid through the application of software engineering principles. This is the third pillar of writing maintainable CSS.

Applying Software Engineering Principles to CSS

There are many different principles for engineering software that lasts.

These principles are responsible for providing a purpose for using the CSS tools and solutions of your choice, through making sure that your CSS code models reality in a maintainable way. Without these principles, using any CSS architecture is mostly a ritual. Writing CSS without respecting software engineering principles tends to collapse under its weight once the size of the code becomes unmaintainable.

If you are a software engineer experienced in some programming languages, you may find the application of these principles to a declarative language like CSS quite surprising. In practice though, CSS has become a mature language, and similarly to other languages, structure is a thoughtful consideration of the code needed. Let us examine some of the main principles in action.

Separation of Concerns

Separation of concerns is a software design principle responsible for defining clearly separated responsibilities in a software solution. The most obvious application to CSS is the separation between classes used for styling and classes used for functionality. Styling classes should not end up in JavaScript code, and functionality related classes should not end up in your stylesheets.

SOLID Principles

Robert C. Martin defined five SOLID principles. Some of these principles apply to CSS just as well as to other programming languages.

In my course on CSS Architectures, you will find many different applications detailing how to use these SOLID principles including the Single Responsibility Principle and the Open-Closed Principle in the context of CSS code.

When it comes to stylesheet hierarchies, we apply the Single Responsibility Principle. For instance, a layer in the ITCSS architecture contains resets or normalizers. Tag styles are built on normalizers, and component styles are built on tag styles. Each layer has one single, clearly defined responsibility.

Possibly the best known example for applying software engineering principles to CSS code is the contrast between DRY and WET CSS. The acronym DRY stands for Don’t Repeat Yourself, while WET stands for We Enjoy Typing.

DRYing out your code leads to better maintainability, because whenever you make a change to DRY code, you can perform that change in just one place with a high degree of certainty that you don’t have to research the rest of your CSS code-base for other occurrences of the same code.

When your CSS is WET, you can DRY it out by identifying the common pieces of your code, and abstracting this common functionality into a base class (or a mixin if using a pre-processor).

Using base classes and child classes in your code is called inheritance, and it is performed with @extend in Sass. When we use mixins, or the @mixin directive using Sass terms, we use composition. Inheritance, composition, and the usage of Sass constants are great tools for performing abstraction.

Experimenting with Composition in CSS

Let’s take a look at a practical example. Suppose we have four types of rectangles in our codebase. A generic one, a rounded rectangle, a green rectangle, and a rounded green rectangle.

We might mark up each rectangle component as follows using the BEM naming convention:

The structure is clear, and we do not repeat ourselves in the modified classes. However, creating a hierarchy of five modifiers would yield in 31 class definitions, where most of our definitions would contain nothing more than a collection of @extend directives.

Composition gives us a more robust structure. In order to create a fully flexible structure, all we need is the generic rectangle class, and two mixins:

If the feature box is rounded, but not green, all we need to do is extend the rectangle class, and include the mixin that makes the rectangle rounded:

.my-rectangle {
@extend .rectangle;
@include rounded;
}

The structure stays flexible, without the overhead for defining classes for each combination.

Towards a Better CSS Architecture

We can conclude that software engineering principles apply to CSS as to any other programming language.
These principles lie between two levels: the micro level of CSS building blocks, and the macro level structuring of these building blocks. As a consequence, it is beneficial to learn how to apply these principles in practice, when it comes to creating maintainable CSS.

To help explain and demonstrate practical application of each of these principles, I have created a course on rock solid CSS architecture; Principles of CSS Architecture

In this course, we explore all three pillars of CSS architecture, putting a lot of emphasis on software engineering principles. You will not only learn these principles in theory, but you will also get a chance to use them in many practical examples.

For instance, we will take a bunch of blog posts, and identify why the supplied CSS code is unmaintainable. We walk through the process of refactoring the CSS step by step, applying the principles briefly presented in this article and covered in depth within the course videos.

I’ve dedicated a complete section on putting the three pillars of CSS architecture into practice by creating a small component library using the ITCSS architecture and Sass. If you are interested in learning more about CSS architectures, sign up for the course, and see you inside!

Zsolt has been working hard on both his tech skills and soft skills, giving him the opportunity to become a Dev Tech Lead. He has taken these skills to the next level by blogging about his dev journey and creating Dev Career Mastery; a learning platform for developers wanting to expand their soft skills.