Upcoming Course: Code Your Own Business w/ React + GraphQL!
We're live-coding on Twitch! Join us!
Solve large scale CSS bottlenecks with ITCSS and BEM

Solve large scale CSS bottlenecks with ITCSS and BEM

I once worked on a frontend codebase where project requirements and sometimes scopes were constantly changing. When requirements change and you adjust the stylesheet or extend the styles of a selector in a particular page, such changes often affects other views that share similar styles.

This may lead to many hours of specificity wars while trying to override styles. Why did all this happen? It happened because the project was not structured in a proper way that would ensure that changes to the stylesheets would not have a negative impact on the entire project.

Common difficulties faced while working with CSS

CSS as a language has a few issues that make managing it a bit difficult for developers.

  • It is declarative, meaning that it does not have a control flow that tells you the state of a project.
  • Everything exists on the global scope making it possible for style collision.
  • There is also the issue of specificity which determines the style that is being applied to a particular selector.

These issues become much more evident when a project grows and more developers are working in the same codebase. If proper structures are not put in place, then changes to the stylesheets tend to become unpredictable.

In order to properly structure CSS in a way that would help us avoid these issues, Harry Roberts created the “Inverted Triangle CSS (ITCSS)”. It is a methodology that can help us structure CSS in such a way that they behave in a more predictable manner. It was designed to help developers working in large scale projects to better organize and manage their CSS.

According to Harry, ITCSS is a collection of principles and metrics by which developers should group and order their CSS to keep it scalable, terse, logical, and manageable. It’s a methodology that involves visualizing a CSS project as a layered, upside-down triangle. This hierarchical shape represents a model that will order CSS by metrics defined by the design of the language with better scalability and maintainability than we'd get if we were to write CSS around how a person thinks.

ITCSS is also flexible and can be used with or without preprocessors. It is compatible with other CSS methodologies like BEM, SMACSS or OOCSS. As we progress in this article, I will show you how you can use ITCSS with BEM.

Basic principles of ITCSS

No id's in CSS

ID's have high selector specificity and this can make it difficult to override styles. To override an ID, we add more IDs or use !important, which can begin specificity wars in your stylesheets. Overriding and extending styles shouldn’t be difficult so as to make your CSS maintainable and pleasant to work with.

Component UI pattern

A component is a collection of reusable markups usually with some logic and styling. Components are self-contained and they serve as building blocks of modern frontend technologies: React, Angular, Vue, Ember, etc. ITCSS favors componentized UI architecture over pages.

Class-based architecture

A good CSS architecture is one that is reusable and flexible. ITCSS favors the use of classes since they can be used multiple times on the same page and multiple classes can be placed in a particular element. Classes also make HTML easy to read.

Key metrics of ITCSS

ITCSS orders your project using three key metrics

Generic styles to explicit styles.

Start out with generic, high-level and far-reaching selectors and then gradually proceed to explicit and more specific selectors. You can start with browser reset rules and then proceed to element selectors and right through to explicit rules such as btn-primary.

Low specificity to high specificity

Every CSS rule has a specificity weight which means that it could be more or less important than the others or equally as important. This weight defines which properties will be applied to an element when there are conflicting rules. Keeping less specific rules at the top and high specific rules under them ensure that your styles cascade properly to avoid conflicts and specificity wars.

Far-reaching to localised

At the start of a project, CSS rules affect a lot of the DOM, but as we progress through the project the rules affect less of the DOM and become more specific to components. We might start by wiping the margins and paddings off everything. Next, we might style every type of element, then narrow it down to every type of element with a certain class applied to it, and so on. It is this gradual narrowing down of reach that gives us the triangle shape.

Benefits

Following these three key metrics has a lot of benefits, to mention a few:

  • Global and far-reaching styles are shared more effectively and efficiently.

  • The probability of specificity wars is reduced since CSS is written in a logical and progressive order in terms of specificity.

  • CSS becomes more extensible and redundant styles are avoided leading to lesser file sizes, and latency.

  • We spend very little time undoing things because our cascade and specificity are all pointing in the same direction

ITCSS Layers

Breaking our CSS into layers helps us stick to the above mentioned metrics. Each layer is added in a location that aligns with the metrics. Each layer is usually a logical progression of the previous layer. As we progress through the layers, explicitness and specificity increases and the reach of the selectors are narrowed.

This means that CSS becomes easy to scale since we are writing it in an order that only adds to what was previously written. It also means that everything has its own predictable location to live, which makes it easy to find and add styles.

ITCSS has seven layers which we would take a closer look at here:

Settings

This is the starting point when using CSS preprocessors. It contains global variables that can be accessed from anywhere within your CSS project. Examples of global settings are font sizes and colour definitions.

Tools

This contains global functions and mixins and it comes after the settings since the mixins and functions might need access to global settings. This layer is the second layer if you are using preprocessors. Examples are font-size mixins px-to-rem mixins, etc. It is important to avoid writing actual styles in the first two layers.

Generic

This is the point where actual CSS is written and it is also the starting point if you are not using a preprocessor. It contains styles such as CSS reset roles, global box-sizing rules, and CSS normalising rules. The styles here affect much of the DOM.

Element

This layer contains element selector styles. It contains styles for bare and un-classed HTML elements. Styles for form, heading, img, links and table elements also come in here. Styles here are still very low-specificity but affects slightly less of the DOM.

Objects

This is the first layer with class-based selector. It contains styles for non-cosmetic objects such as containers, wrappers and layout systems. This layer affects less of the DOM than the last layer, has a higher specificity, and is slightly more explicit given that we are now targeting specific sections of the DOM with classes.

Components

This layer holds the style for each component in your project. It’s more explicit than the previous layer because we are now styling visible UI elements. In component based frameworks like Angular, Vue or React, this is the layer where you import your styling for each component if you don’t include them directly in your component.

Trumps

This layer beats other layers. This is where utility and helper styles are defined. It is specificity heavy, can override previous styles and is the tip of the triangle. Most of the styles here contain the !important flag.

The result

When all the layers are combined together, it could look like this.

// main.scss
@import "settings.fontsize";
@import "settings.colors";

@import "tools.functions";
@import "tools.mixins";

@import "generic.reset";
@import "generic.normalize";

@import "elements.forms";
@import "elements.table";

@import "objects.contianer";
@import "objects.grid";

@import "components.site-nav";
@import "components.buttons";
@import "components.carousel";
@import "trumps.clearfix";
@import "trumps.utilities";

Using BEM with ITCSS

Block Element Modifier(BEM) is a methodology for naming CSS styles. According to Getbem, BEM is a highly useful, powerful, and simple naming convention that makes your front-end code easier to read and understand, easier to work with, easier to scale, more robust and explicit, and a lot more strict.

BEM is particularly useful for the six and seventh layer of ITCSS where class-based selectors are heavily used. Following BEM would ensure that classes are properly named for better readability.

Exemplifying the BEM methodology

B **(Block**) A block is a top-level node, the highest level abstraction of a component. It is a standalone entity or a component, on a web page.

Example:

.btn {
  rules
}

E **(Element**) Elements are children of a block. They have no standalone meaning and are semantically tied to a block. Elements are prefixed with their block name and a double underscore.

Example:

.btn__text {
  rules
}

.btn__icon {
  rules
}

M **(Modifiers**) Modifiers are used to change the appearance of a block or an element. They provide us with a way of creating variations of blocks and elements rules.

.btn--lg {
  rules
}

.btn__text--light {
  rules
}

.btn__icon--right {
  rules
}

When there is a need for change in your codebase, you don’t want to spend time overriding styles and fighting specificity wars. The inverted triangle architecture makes sure that there is no war by helping you visualise the state of your entire codebase in very simple terms.

Following its principles helps create a system that is approachable, predictable, guessable, rule based, prone to smaller file sizes, and geared toward scalability and growth.

If you're working on a front-end project that makes heavy use of CSS, i would recommend using ITCSS with BEM so as to make your code easier to read and understand. This will not only ensure good maintainance of the code but also promote empathy for oncoimg developers.

Like this article? Follow @kenny_io on Twitter