Modern, Clean and scalable CSS

Mateusz Tondos

Maintaining large-scale CSS codebase is a demanding task. In the era of complex component systems and unspecified requirements, this gets even harder – what solves many possible problems, is well-organized, clearly defined architecture.

CSS Cascade in essential follows simple statements: rules matched to selectors and selectors matched with elements in ascending order, according to their placement. Around this simple definition, and abilities that CSS preprocessors gave us, lots of architecture patterns were built. They all try to make development easier, focusing on different aspects of how the codebase is built.

Having a glance at CSS codebase from a very high point of view might reveal some distinct parts:

  1. Techniques used – a finite set of tricks that style content
  2. Visible rules and conventions
  3. Categorization of declarations – a high-level skeleton that is used to classify declarations in abstract categories.

Following rules and abstracting declarations into categories is not necessary for styles to do their job. They could still work while being messy, ungrouped and placed together in one large CSS file. CSS gives you a lot of freedom – there are no language-specific rules that force you to define certain project structure and follow good practices, but it is highly recommended to do so.

Moving on to second point, let’s see what could be done here to make styles cleaner and more scalable. In the examples, Sass is used.

Good practices

Naming conventions

BEM stands for block-element-modifier and is a class naming convention in format of

.block__element--modifier {}

The goal of it is to quickly inform about element position and state knowing only about the class name.

Practical example

As a practical example assume that our website needs to have horizontal navigation menu, and some vertical menus.

Markup for given design written in BEM could look like

<ul class="menu">
      <li class="menu__item">Home</li>
      <li class="menu__item">Contact</li>
      <li class="menu__item">Credits</li>
</ul>	

<ul class="menu menu--vertical">
      <li class="menu__item">Article 1</li>
      <li class="menu__item">Article 2</li>
      <li class="menu__item menu__item--underlined">Article 3</li>
</ul>

Wrapping up above, there are four classes defined.

.menu {}
.menu--vertical {}
.menu__item {}
.menu__item--underlined {}

In BEM world, our block is .menu.
A Vertical menu has class .menu–vertical.
Children elements are those with class .menu__item.
There is also underlined children element that has .menu__item—-underlined class.

To fit the design, styles may look like

.menu {
    display: flex;
    flex-direction: row;

    list-style-type: none;
}

.menu--vertical {
    max-width: 400px;
    flex-direction: column;

    list-style-type: circle;
}

.menu__item {
    margin-right: 8px;

    .menu--vertical & {
        margin-right: 0;
        color: goldenrod;
    }
}

.menu__item--underlined {
    text-decoration: underline; 
} 

Default menu has the flex-direction property of row, what makes it span horizontally. Few lines later we define .menu--vertical that overrides .menu flex-direction rule, giving it a vertical direction. If .menu--vertical is present above .menu__item in the markup, children also receive new styles that modify defaults.

Just by having a glance at this CSS we can extract a lot of information. We see that .menu__item must live inside .menu, and that .menu can have two states – default that is horizontal and .menu--vertical that makes it span vertically. Children element .menu__item can also have underlined state. We know it all just from class names.

Caveats

A common mistake made often in early phase of adopting BEM is nesting children more than one level. The class name of HTML element should not look like .block__element_second-element__third-element even when this element is a child of other element in HTML. Use the standard .block__element notation, and when there is no more namespace left, create a new block.

BEM could be easily fitted in higher level design patterns. It adopts well to component-oriented systems.

Creating one file per block is a good idea – brought to OOP world, it matches the rule of one class per file.

Code for this example can be found here

Defining constants

Imagine that client asked for small new feature that need some styles to be consistent with the existing ones, but there is no graphic design provided. In such situation, it’s good to have an easy to use set of pre-defined constants for font-sizesfont-families, spacings, media-query breakpoints and all properties that can be standardized. This helps developers not familiar with project quickly create components cohesive with project style.

$base-spacing-unit: 8px;

$spacing-unit-medium: $base-spacing-unit * 2;
$spacing-unit-large: $base-spacing-unit * 3;

If you decide to declare spacings in pixels, use a base size, and multiples of this number. It could be 8px, as stated above. All numbers that multiply easily would work well.

Even numbers

Assuming you use pixels – using even numbers in spacing is good because it helps you avoid magic numbers and debug fast. When you see an uneven number in your component’s margin, it’s a sign that there might have been an unwanted collision with external library style.

Declaration order and code align

Sorting rules speeds up further changes, just like finding record in database if there is an index. Possible strategies are sorting by name and by type. As far as sorting by name is obvious as it is, dividing properties into groups is not. For example, groups could be defined as those related with Positioning, Box Model and Text.

.selector {
    /* Positioning */
    position: absolute;
    z-index: 10;
    top: 0;
    right: 0;

    /* Display & Box Model */
    display: inline-block;
    box-sizing: border-box;
    width: 100px;

    /* Text */
    font-family: sans-serif;
    font-size: 16px;
    text-align: right;

    /* Other */
    background: #000;
    color: #fff;    
}

Other strategies not stated here, are good as well – just pick one and stick to it.

Avoiding !important

Use selector only as specific as needs to be.

The most common good practice in CSS is avoiding !important – almost everyone heard about this rule. Even though adding !important might quickly fix one bug, it instantly makes fixing another harder.

To avoid it, try to nest the same class that you used before – its friendly for readability and does not feature new context.

.selector.selector.selector {} // tripled class specifity

Sometimes !important is a must – especially if you wish to fix something in a reasonable time, without refactoring whole codebase. Just try to avoid it.

The specifity graph

Let’s discus the specifity of a selector over its location in stylesheet.

It’s graph representation with lots of high-specifity selectors in the middle of a stylesheet might look like this:

In the example above, there are plenty of high-specifity selectors scattered all over the stylesheet, mixed with the ones of small specifity. Such arrangement is not good for your styles – it makes maintenance a lot harder. The specifity graph makes most sense with categorization. Placing higher-specifity utility declarations at the very end of a stylesheet and small-specifity layout defining ones at the beginning helps with unwanted overridden rules.

What you should try to achieve, is a graph with slight specifity growth over location. In other words, the specifity graph should trend upward.

This is an example of good specifity/placement relation. There will never be a flat line – increasing specifity in a project must occur, and it is totally fine as more specific components that need to override abstract ones are created. The specifity graph plays significant role in the ITCSS architecture described in the next paragraph.

Skeleton

For large projects, high-level structure makes code extensible. You should always know where to put new piece of CSS. The ITCSS (Inverted Triangle CSS) methodology proposes putting declarations into several categories. Here is a short introduction to ITCSS catalogs and their purposes:

  1.  Settings – meant to store variables and config.
  2. Tools – sass functions and mixins.
  3. Generic – resets and frameworks like bootstrap.
  4. Elements – default styles for specific HTML elements.
  5. Objects – abstract layout elements. Good example of what should live in objects, would be a grid system or a container with its variations.
  6. Components – reusable, decoupled, independent pieces of UI.

Those categories are the backbone for everything that happens in ITCSS. Specifity and explicitness grow with order, being fitted properly into parts of the specifity graph. The categories proposed in ITCSS methodology divide UI into meaningful parts, with full respect of reusability and scalability.

In a world of Javascript and its frameworks, not much attention is set on the UI. Components are spitted between modules considering their functionality. ITCSS teaches us that it’s time to rethink our frontend architectures. Shouldn’t modern frontend development go back to the roots?

The official ITCSS framework is inuitcss. It could be found under this link

Apart from implementing ITCSS, inuitcss adds useful prefix to element class names. If the class belongs to: Components, Objects or Utilities it receives prefix of c-, o-, u-. This makes classes even more useful. Example class name might look like .c-header__item--large

Summary

Stated techniques might help with maintaining large CSS codebase. Specific rules should be defined as soon as possible and followed by everyone that contributes to the project. A good set to start with may feature:

  • Using naming convention, such as BEM
  • Using style variables
  • Using even numbers with pixels
  • Sorting rules
  • Giving selector only as much specifity at it needs
  • Categorizing declarations

CSS is often threated secondarily even in frontend-focused projects. The codebase may grow long until first scalability problems start to appear. To avoid such situation, rules have to be defined in the early phase of development, making project as consistent as possible.

Poznaj mageek of j‑labs i daj się zadziwić, jak może wyglądać praca z j‑People!

Skontaktuj się z nami