Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Utility-first CSS and CSS modules

BEM solves the problem of the shared name space with an agreement: people themselves make sure names do not collide. There are two other paths. The first is to hardly invent names at all, building the look from ready-made small classes. The second is to let build tools make names unique.

Utility classes

A utility is a class that does one thing: sets a padding, a color or a corner radius. The utility-first approach builds the look right in the markup from such classes:

<style>
  .p-3 { padding: 12px; }
  .rounded { border-radius: 8px; }
  .border { border: 1px solid silver; }
  .bg-light { background: whitesmoke; }
  .bold { font-weight: bold; }
  .text-green { color: seagreen; }
</style>
 
<div class="p-3 rounded border bg-light">
  <div class="bold">Backpack</div>
  <div class="text-green">€40</div>
</div>

There is not a single rule about a card in the CSS — the card came out of a combination of utilities.

Tailwind

The best known utility library is Tailwind CSS. It has thousands of ready-made classes like p-4, bg-white, rounded-lg, hover:bg-gray-100, and only the ones found in the markup end up in the final file. Pros: no names to invent, the CSS barely grows, the styles are visible right in the HTML. Cons: long class attributes and a vocabulary of classes to learn.

CSS modules

CSS modules are a feature of bundlers: programs that prepare a project's code for publishing. You write ordinary short names, and the bundler makes them unique automatically — for example, .title becomes .Card_title_x7f2:

/* Card.module.css contains: .title { color: rebeccapurple; } */
import styles from './Card.module.css';
 
export function Card() {
  return <h3 className={styles.title}>Backpack</h3>;
}

The .title class from this file will never collide with another component's .title. The approach is popular in React projects.

Scoped styles in frameworks

The JavaScript frameworks Vue, Svelte and Angular can limit styles to a component: the rules from its style block apply only to its markup. In Vue this is done with the scoped attribute:

<template>
  <h3 class="title">Backpack</h3>
</template>
 
<style scoped>
.title { color: rebeccapurple; }
</style>

There are no name conflicts, and dead CSS is deleted together with the component file.

What to choose

SituationA fitting approach
A regular site without a frameworkBEM, optionally with Sass and @layer
Quick prototypes, a small teamutilities or Tailwind
A React projectCSS modules or Tailwind
A Vue or Svelte projectscoped styles

Approaches get mixed: components in BEM or in modules plus a dozen utilities like .visually-hidden. What matters is that the team agrees.

Common mistake

Copying the same long set of utilities into every place:

<style>
  .px-3 { padding: 6px 12px; } .rounded { border-radius: 6px; } .bg-green { background: seagreen; }
  .text-white { color: white; } .no-border { border: none; }
</style>
 
<button class="px-3 rounded bg-green text-white no-border">Buy</button>
<button class="px-3 rounded bg-green text-white no-border">Add to cart</button>
<button class="px-3 rounded bg-green text-white no-border">Check out</button>

To change the color of all buttons, you would have to fix each one. A repeated set is moved into a framework component or an ordinary .button class.

Thanks for reading!

Donate to the site — we work hard for you.