CSS Modules

Originally published in Spanish on .
Read the original article.

CSS preprocessors make it possible to improve our workflow. One example is separating content into modules and applying some ideas from OOCSS, which improves the quality and speed of our code.

OOCSS

With the OOCSS approach, we can design styles in a way that resembles a programming language. Before describing how modules work, I will briefly review a few elements that make it possible to divide our styles.

Avoid cascading styles

Take this code as an example:

a {
  color: olive;
  text-decoration: none;
}

It looks harmless, and it works well in simple documents. However, when we want to style the links in a menu, for example, we have to override some rules; otherwise, the links will not have the color we want.

.menu a {
  color: white;
  background-color: green;
}

And when we start styling links in articles, we modify the style again:

article a {
  text-decoration: underline;
}

This repeats for every section of the document, which makes us wonder how useful global styles really are. In programming languages, we generally avoid global declarations and reserve them for specific situations. The same seems to apply to CSS: global variables should be reserved for special cases, such as applying a reset or a few well-known hacks.

If we look at the cause of the problem, we find that HTML tags establish relationships, enable interactions, or give meaning to their content, but they can appear in many contexts. One solution is to define their context and the relationships they may have. A menu, for example, might have this HTML structure:

<nav class="menu">
  <ul>
    <li><a href="#">Link 1</a></li>
    <li><a href="#">Link 2</a></li>
  </ul>
</nav>

It is a somewhat independent section of the page that performs a specific function. If, instead of declaring global rules, we place them within the context where they appear, we can avoid cascading styles. Our rules would look like this:

.menu a { }
.menu li { }
.menu ul ul { }

Specificity

On the other side of cascading rules is specificity: the sum of declarations in a rule. For example:

.menu a {}
#main .menu ul li a { }

The second rule wins because, in stylesheets, IDs have more weight, followed by tags and then classes. To declare a rule with higher specificity than the second one, we would need to add another ID or write !important at the end. The first rule is enough to apply only to menu links; there is no need to add more selectors.

Specificity becomes more common with preprocessors because they allow nested rules. Before we realize it, we end up with selectors like the second example.

Modules

As we have seen, we should seek balance when defining selectors: they should not be global, but neither should they be overly specific. With that in mind, I think an effective way to organize code is to split styles into small files that belong to a context, which we can call modules1. We would create one file for articles, another for products, another for the footer, and so on—sections of the page that work as a unit.

To begin, we create a controller that includes modules as well as variables (config) and support files (helpers).

/* controller.less */

@import helpers/reset.less
@import config/colors.less
@import config/layout.less

@import modules/menu.less
@import modules/comment.less

When we use a preprocessor, each of these imports does not generate an HTTP request because compilation happens before publishing. The file structure would look like this:

- Styles/
-- controller.less
-- Modules/
---- menu.less
---- comments.less
-- Helpers/
---- reset.less
-- Config/
---- layout.less
---- fonts.less

Both the helpers and config files deserve a more detailed explanation, but to keep the idea of modules focused, I will leave them for another time.

Structure of a module

Modules bring together the three concepts we have seen: avoiding cascading styles, specificity, and preprocessors. The following example shows how to declare a module with those characteristics:

/* Comments.less */

.comments {
  .comment {
    width: 100%;
  }

  .comment .comment {
    padding: 5%;
  }
}

.comment {
  border-color: red;

  .content {
    color: gray;
  }

  .avatar {
    max-width: 20%;
  }

  .author {
    font-size: 1.5rem;
  }

  .date {
    font-style: italic;
  }
}

Generally, we will have two groups: one plural and one singular. Notice that the .comments class is repeated: the first time it contains only the external aspects, while the second group defines internal aspects. This avoids adding more specificity than necessary.

We also avoid using HTML tags as selectors, which lets us change semantic structure without changing styles. In other words, if a comment tag is introduced instead of article, or we decide the current tag is not appropriate, classes in the styles let us change tags without affecting the design. Nicolas Gallagher explains this topic better in his article on semantics.

With this structure, we can create files for articles, products, the footer, and every other module on the page.

Footnotes

  1. In systems such as BEM, they are known as blocks, and there may be other names for them. Still, I find the word modules appropriate because they define individual sections of a document, can be combined, and can be easily exported.