DEV Community

Cover image for Organizing styles better with BEM + ITCSS + Sass
Sivantha Paranavithana
Sivantha Paranavithana

Posted on

Organizing styles better with BEM + ITCSS + Sass

Writing CSS is easy.

But maintaining them is not!

On smaller projects, how you organize your code isn’t usually a big concern. However, when it comes to larger, complex projects, how you write and organize code can affect at least in three ways.

  1. How long it takes to write code.
  2. How much code you have to write.
  3. How much loading your browser have to do.

Organizing code becomes really important when you are working with a team. The code you write should be transparent. In other words, it should be clear and obvious to understand. And also they should be consistent. Having consistent code throughout the project reduces the amount of mental overhead needed when writing new code, debugging or refactoring.

Another importance of organizing code is to make the code self documenting as much as possible. In that way we don't have to lose time in writing or reading lengthy, supplementary documentation or give boring KTs to other developers.

There are plenty of methodologies to reduce the CSS footprint, organize collaboration and maintain large CSS codebases. Among them the mix of BEM, ITCSS and the power of Sass is the most favorite of mine.

Let me convince you why it should be your favorite as well.

Let's start with ITCSS.

ITCSS

ITCSS is a CSS architecture to make your CSS more scalable and maintainable. ITCSS stands for "Inverted Triangle CSS". What that mean is that dividing the CSS codebase to several sections (called layers), which take the form of an inverted triangle.

ITCSS

ITCSS works with other methodologies such as SMACSS, OOCSS and even BEM. Also, we can take the advantage of the power of a preprocessor such a Sass to make it more flexible and powerful.

The layers in ITCSS are ordered in a way that it takes the full advantage of some of the most fundamental concepts of CSS, cascade, inheritance and specificity.

It start out with the most generic, low-level, catch-all, unremarkable styles, and eventually progress to more explicit and specific rules as we move through the project.

In other words, selectors in the beginning of the triangle affect a lot of the DOM and the reach gets narrower as we go down the triangle.

So it's all about organization. We can use a folder structure as below to organize our styles according to the ITCSS architecture.

// folder structure

theme
│
└─── settings
│    ├── _settings.colors.scss
│    ├──  _settings.fonts.scss
│    ├──  ...
│    └─── _index.scss
│
└─── tools
│    ├──  _tools.gradients.scss
│    ├──  _tools.font-sizing.scss
│    ├──  ...
│    └─── _index.scss
│
└─── generics
│    ├──  _generics.page.scss
│    ├──  _generics.normalize.scss
│    ├──  ...
│    └─── _index.scss
│
└─── elements
│    ├──  ...
│    └─── _index.scss
│
└─── objects
│    ├──  ...
│    └─── _index.scss
│
└─── components
│    ├──  ...
│    └─── _index.scss
│
└─── trumps
│    ├──  ...
│    └─── _index.scss
│
└─── _index.scss
Enter fullscreen mode Exit fullscreen mode

Note that here we are using a Sass concept called partials for stylesheets inside layers. A partial is a small chunk of styles which we can later import into another sass file. We name partials with a preceding underscore.

Inside each layer we create an _index.scss file. Inside that file, we use the @forward rule in sass to combine everything inside that layer and forward it as a module.

// theme/settings/_index.scss

@forward './settings.colors';
@forward './settings.fonts';
Enter fullscreen mode Exit fullscreen mode

Then we import all the modules into the _index.scss file using the @use rule as below.

// theme/_index.scss

@use './settings';
@use './tools';
@use './generics';
@use './elements';
@use './objects';
@use './components';
@use './trumps';
Enter fullscreen mode Exit fullscreen mode

These _index.scss files are optional and we use this pattern only if we want a single global style file. Otherwise it is recommended to use each layer as modules using @use rule or use individual partials by themselves.

You can learn more about sass "at-rules" in here.

Notice that in each sass at-rule (@forward or @use), underscore and the '.scss' extension is omitted

Here we have used @use rule instead of @import since Sass recommend the use of @use over @import for many reasons. You can read more about that in here

Now let's take a look at each layer of ITCSS.

Settings

This layer holds any global settings for your project. We should include settings which can be accessed from anywhere in this layer. For examples, font size, color palettes and configuration available for the entire project should be included in this layer.

In here we can use Sass variables to define colors, fonts used in the project as settings, then we can use that variable around the project.

// theme/settings/_settings.color.scss

$primary: #ea691e;
$secondary: #ff9988;
$primary-light: #fbcfc7;
Enter fullscreen mode Exit fullscreen mode
// theme/components/_components.button.scss

@use '../settings/settings.colors' as colors;

.example-button {
    background-color: colors.$primary;
}
Enter fullscreen mode Exit fullscreen mode

When using variables, mixing, etc. inside another partial, it is recommended to use the @use rule with an alias instead of using @import to import everything inside the partial.

Because, if we import the same stylesheet more than once, it will be evaluated again each time. If that stylesheet just defines functions and mixins, this usually isn’t a big deal, but if it contains style rules they’ll be compiled to CSS more than once.

With this approach, if you realize you need to change something, for an example the theme color, you only need change it in one place.

Tools

This layer houses the globally available tooling, namely mixins and functions.

Note that in here as well, we only keep globally available stuff in here. Any mixin or function that does not need accessing globally should belong in the partial to which it relates.

Tools layer comes after the Settings layer because, these mixins and functions may need global settings as parameters.

Examples for tooling that goes inside this layer are gradient mixins, font-sizing mixins, etc.

// theme/tools/_tools.gradients.scss

@mixin linear-gradient($direction, $colors) { 
  background: -webkit-linear-gradient($direction, $colors);
  background: -moz-linear-gradient($direction, $colors);
  background: -o-linear-gradient($direction, $colors);
  background: linear-gradient($direction, $colors);  
}
Enter fullscreen mode Exit fullscreen mode

Generics

The Generic layer is the layer that houses very high-level, far reaching styles. It contains things like global box-sizing rules, normalize styles, CSS resets, etc.

// theme/generics/_generics.input.scss

input[type="search"]::-webkit-search-decoration,
input[type="search"]::-webkit-search-cancel-button {
  -webkit-appearance: none;
}
Enter fullscreen mode Exit fullscreen mode

Elements

This layer includes the styles which are applied to bare HTML elements. We basically use HTML tag selectors in this layer. This is still a very low-specificity layer, but affects slightly less of the DOM, and is slightly more opinionated, hence its location in the Triangle.

// theme/elements/_elements.headings.scss
@use '../settings/settings.fonts' as fonts;

h2 {
  font-size: 25rem;
  font-family: fonts.$primary;
}
Enter fullscreen mode Exit fullscreen mode

Typically, this layer is the last layer we'd find bare HTML element selectors. After this layer we'll basically be using classes to define everything else.

Objects

This is the first layer in which we find class selectors. As the name imply, we write the styles aiming objects which defines undecorated design patterns. Such objects can range from something simple as a .container of layout systems to complex object with many children.

If we are going to combine our partials to a single file, it's better to prefix everything inside our partials some keyword to avoid conflicts between class names.
(We have used xmpl as our prefix from here on)

// theme/objects/_objects.container.scss
@use '../settings/settings.breakpoints' as breakpoints;

.xmpl-container {
  width: 100%;
  padding-right: 15px;
  padding-left: 15px;
  margin-right: auto;
  margin-left: auto;

  @media screen and (max-width: breakpoints.$medium) {
    max-width: 720px;
  }

  @media screen and (max-width: breakpoints.$small) {
    max-width: 540px;
  }
}
Enter fullscreen mode Exit fullscreen mode

Note that here we have used nesting to add the @media queries to the class.

This layer affects less of the DOM than the last layer, yet making modifications to the classes inside this layer could potentially have effects in a lot of other unrelated places.

Components

This I where we begin to style the recognizable parts of our UI. So, this is where the majority of our work takes place. In here also we are using classes for defining styles. Therefore the specificity has not increased yet. We shouldn't find any selectors with a lower specificity than one class in this layer.

// theme/components/_components.button.scss

.xmpl-button {
  display: inline-block;
  font-weight: 400;
  text-align: center;
  white-space: nowrap;
  vertical-align: middle;
  border: 1px solid transparent;
  padding: .375rem .75rem;
  font-size: 1rem;
  line-height: 1.5;
  border-radius: .25rem;
  transition: all 0.5s ease;
}
Enter fullscreen mode Exit fullscreen mode

Trumps

This is the highest specificity layer. It includes the most explicit types of rule, with the most narrow focus.

Typically this layer beats (trumps) all other layers, and has the power to override anything at all that has gone before it. This layer contains utility and helper classes, hacks and overrides.

A lot of the declarations in this layer will carry !important tag.

// theme/trumps/_trumps.utilities.scss

.text-center {
  text-align: centre !important;
}
Enter fullscreen mode Exit fullscreen mode

So, with ITCSS we are breaking our CSS codebase into groups based around specificity, reach and explicitness. This format allows us to write our CSS in an order that only ever adds to and inherits from what came previously.

So, now you might be wondering (or not),

  • what if I want to change the h2 tag in a specific page?
  • Where do I put that style?
  • Should it go inside the elements layer in the headings partial, or should I define those kind of style outside the ITCSS folder structure?
  • Maybe inside another separate stylesheet or go with in-line styles?

Well, personally I don't like inline styles. And defining those styles outside the ITCSS folder structure in a different stylesheet is not right as well.

So, if we need to change the styles of the h2 tag in a specific page (let's call it my-page), what we have to do is create a _components.my-page.scss inside the components layer and create a specific class for that h2 element and bind that h2 element to that class instead of binding to the html element directly.

<!-- my-page.html -->

<div>
   <h2 class="xmpl-my-page__title">Hello world</h2>
</div>
Enter fullscreen mode Exit fullscreen mode
// theme/components/_components.my-page.scss

.xmpl-my-page__title {
  color: #ffffff
}
Enter fullscreen mode Exit fullscreen mode

In my experience, with this approach we end-up creating so many component partials inside the component layer as well. Therefore, I prefer to maintain another layer right below the components layer called features or pages to keep these kind of styles separate. But it's not in the ITCSS architecture. It's just my preference 🤓.

So, that's pretty much everything (As I know) about the ITCSS architecture. Right now we have organized all of our stylesheets (files) in a scalable and maintainable manner.

Now let's move into how we can organize the code we write inside all the partials we have created to house them.

You might have noticed in the above example that I have used a strange way to name my class (xmpl-my-page__title). Well, that's the BEM convention. Let's dig deeper on that topic.

BEM

BEM stands for "Block Element Modifier". It is a naming convention that makes our code more consistent, transparent, scalable and maintainable. The BEM approach ensures that everyone in the team works with a single codebase and speaks the same language.

Now, what are Blocks, Elements and Modifiers?

Blocks

Blocks are the standalone entities which are meaningful on its own. For examples, we can identify a header, a list, a menu, a checkbox, an input, etc. as an entity.

Elements

An element is a part of a block that has no standalone meaning and is semantically tied to its block. For an example, we can identify a single item of a menu as an element, or the caption of a checkbox, or an item of a list, etc.

We use __element suffix to denote elements in BEM approach.

Modifiers

A modifier is a flag on a block or an element. We can use them to identify a change appearance or behavior of a particular block or an element. For an example, we can identify 'disabled' as the modifier in a disabled menu item, or the 'color yellow' of a container, etc.

We use --modifier suffix to denote elements in BEM approach.

So, how can BEM be any help?

Either we follow ITCSS or not, we have to write CSS class names sooner or later. But when naming those classes, we need to have a specific way of doing that. That's where BEM comes into play.

We can use the BEM's Blocks, Elements and Modifier pattern to name each class we create.

But why is it REALLY helpful?

The power of BEM comes with a preprocessor such as Sass.

Let's see why with an example.

<button>
   <img src="/assets/icon.svg" />
   <span>Submit</span>
</button>
Enter fullscreen mode Exit fullscreen mode

Let's say we need to style the above button which has an icon inside (A sharable component). Also we need to have three background colors for the button to support default, success and danger scenarios as below.

Final result

Let's try to follow the previously learned ITCSS style as well.

In this case, first we need to create a partial to hold this component. Let's name it _components.button.scss. Then we need to identify Blocks, Elements and Modifiers. So, in this case the Block would be the <button> itself. We can identify the <img> & <span> as elements. And we can identify default, success and danger as modifiers.

Now we can get into naming classes.

We can name the Block as btn since it is a button. From there we start to name elements as below.

<button class="btn">
   <img class="btn__icon" src="/assets/icon.svg" />
   <span class="btn__text">Submit</span>
</button>
Enter fullscreen mode Exit fullscreen mode

What we have right now is this.

Step 1

With that we can define the styles as below.

// theme/components/_components.button.scss

.btn {
  padding: 0.5rem 1rem;
  margin: 1rem;
  display: flex;
  border-radius: 10px;
  justify-content: center;
  align-items: center;
  background-color: #cdcccc;

  &__icon {
    margin-right: 0.5rem;
  }

  &__text {
    font-size: 15px;
    color: #000000;
  }
}
Enter fullscreen mode Exit fullscreen mode

This is the beauty of BEM + Sass. We can nest the classes using the & operator of Sass. What it does is, basically denoting the parent objects name. So that &__icon equals to .btn__icon and &__text equals to .btn__text. Since here we have the BEM's Blocks and Elements notations as well, nesting like this doesn't make our code unreadable.

After applying those styles, now we have this.

Step 2

Since we need three buttons to show three states, we can create three btn blocks with the modifiers to denote each state.

<button class="btn">
   <img class="btn__icon" src="/assets/icon.svg" />
   <span class="btn__text">Submit</span>
</button>

<button class="btn btn--success">
   <img class="btn__icon" src="/assets/icon.svg" />
   <span class="btn__text">Submit</span>
</button>

<button class="btn btn--danger">
   <img class="btn__icon" src="/assets/icon.svg" />
   <span class="btn__text">Submit</span>
</button>
Enter fullscreen mode Exit fullscreen mode

Now we have this,

Step 3

Notice that we haven't used the --default modifier since, it is not needed as we already have the default state without any modifiers.

Now we can take the advantage of the BEM's modifiers in the stylesheet to create the other two states.

// theme/components/_components.button.scss

.btn {
  padding: 0.5rem 1rem;
  margin: 1rem;
  display: flex;
  border-radius: 10px;
  justify-content: center;
  align-items: center;
  background-color: #cdcccc;

  &__icon {
    margin-right: 0.5rem;
  }

  &__text {
    font-size: 15px;
    color: #000000;
  }

  &--success {
    background-color: #709558;
  }

  &--danger {
    background-color: #ca5b5b;
  }
}
Enter fullscreen mode Exit fullscreen mode

We have nested the --success and --danger modifiers inside the .btn class, so that we can get the resulting classes as .btn--success and .btn--danger as we have used in our HTML.

Also notice that we haven't changed the btn class name in our HTML. Instead, we have added the btn--success class as another new class to the class list. Doing so, the styles defined under the .btn class also get applied to the button and only the background-color property gets replaced when the modifier class get applied.

Now we have this,

Step 4

There are few more changes have to be done. We have to invert the color of the icon in success and danger states. And we also have to change the color of the text to white in that states as well.

For this we can introduce more modifiers to our HTML like below and use them to style them.

<button class="btn">
   <img class="btn__icon" src="/assets/icon.svg" />
   <span class="btn__text">Submit</span>
</button>

<button class="btn btn--success">
   <img class="btn__icon btn__icon--inverted" src="/assets/icon.svg" />
   <span class="btn__text btn__text--white">Submit</span>
</button>

<button class="btn btn--danger">
   <img class="btn__icon btn__icon--inverted" src="/assets/icon.svg" />
   <span class="btn__text btn__text--white">Submit</span>
</button>
Enter fullscreen mode Exit fullscreen mode
// theme/components/_components.button.scss

.btn {
  padding: 0.5rem 1rem;
  margin: 1rem;
  display: flex;
  border-radius: 10px;
  justify-content: center;
  align-items: center;
  background-color: #cdcccc;

  &__icon {
    margin-right: 0.5rem;

    &--inverted {
      filter: invert(1);
    }
  }

  &__text {
    font-size: 15px;
    color: #000000;

    &--white {
      color: #ffffff;
    }
  }

  &--success {
    background-color: #709558;
  }

  &--danger {
    background-color: #ca5b5b;
  }
}
Enter fullscreen mode Exit fullscreen mode

With that, now we have the final result.

Final result

Additionally, we also can prefix the .btn class at the root with some keyword (eg: .xmpl-btn) so that we won't get any conflicts in class names later on.

That's how we can take the advantage of BEM to write more organized CSS.

There are 3 major benefits we get with BEM approach.

Modularity

Block styles are never dependent on other elements on a page, so you will never experience problems from cascading.

You can also export the blocks from your old finished projects to new ones with ease.

Reusability

Composing independent blocks in different ways, and reusing them intelligently, reduces the amount of CSS code that you will have to maintain.

Also, with the power of a preprocessor such as Sass, you can further reduce the amount of code you have to write using nesting.

Structure

BEM methodology gives your CSS code a solid and consistent structure that remains simple and easy to understand.

That's it folks. It was quite a long article. But I hope this will be helpful guide for you.

See you in the next article. ✌️

Discussion (4)

Collapse
theqwertypusher profile image
Jason Victor

Definitely a nice dive. thanks!

Collapse
sivantha96 profile image
Sivantha Paranavithana Author

You're welcome 😃

Collapse
andberry profile image
Andrea Berardi

Great post, thank you so much for sharing!

Collapse
sivantha96 profile image
Sivantha Paranavithana Author

You're welcome 😃