Skip to content
Laviyx Learn

Try

CSS course menu

Organizing CSS: BEM Naming, Utility Classes, Cascade Layers and File Structure

Learn how to organize CSS in real projects: why large stylesheets get messy, BEM naming for components, utility classes, CSS Modules and scoping, cascade layers with @layer, a sensible file structure, and linting with Stylelint.

Advanced6 min readUpdated
On this page

Small stylesheets are easy. Problems start when a site has hundreds of components and several developers: rules override each other unexpectedly, nobody dares to delete old CSS, and every fix needs a more specific selector. CSS architecture is a set of habits that keeps stylesheets predictable.

The usual problems#

  • Global scope: every rule can affect every element on every page.
  • Specificity wars: #header .nav ul li a needs an even longer selector to override it, then !important.
  • Unclear dependencies: it is hard to know which HTML a rule affects, so dead CSS piles up.

The approaches below each attack these problems.

BEM naming#

BEM (Block, Element, Modifier) is a naming convention that keeps every selector a single class:

PartMeaningExample
BlockA standalone component.card
ElementA part of the block, joined with __.card__title, .card__image
ModifierA variant or state, joined with --.card--featured, .card__title--small
CSS
.card { padding: 12px; border: 1px solid #ddd; border-radius: 8px; max-width: 280px; margin-bottom: 10px; }
.card__title { margin: 0 0 4px; font-size: 1.1rem; }
.card__price { color: #2f9e44; font-weight: bold; }
.card--featured { border-color: #2f5fe0; background: #eef3ff; }
HTML
<div class="card">
  <h3 class="card__title">HTML course</h3>
  <p class="card__price">Free</p>
</div>
<div class="card card--featured">
  <h3 class="card__title">Web bundle</h3>
  <p class="card__price">Free</p>
</div>
Result

Benefits: all selectors have the same low specificity, names say exactly where a rule is used, and components do not leak into each other. The names are long, which is the trade-off.

Utility classes#

The opposite approach: many tiny single-purpose classes, combined in the HTML:

HTML
<div class="p-3 border rounded bg-light">
  <h3 class="m-0 text-lg">HTML course</h3>
</div>
CSS
.p-3 { padding: 1rem; }
.m-0 { margin: 0; }
.border { border: 1px solid #ddd; }
.rounded { border-radius: 8px; }

You rarely write new CSS, and styles never conflict, but the HTML gets crowded. Frameworks such as Tailwind CSS are built entirely on this idea (CSS Frameworks). Many projects mix both: components for structure and a few utilities for spacing.

Scoped CSS#

Build tools can scope CSS to a component automatically. With CSS Modules, a class like .title in Card.module.css is renamed to something unique such as Card_title__x7a2 when the site is built, so it can never clash with another .title. Frameworks such as Vue and Svelte, and the Shadow DOM in Web Components, offer similar scoping.

Cascade layers#

@layer lets you decide the priority of whole groups of styles, regardless of specificity:

CSS
@layer reset, base, components, utilities;

@layer components {
  #main .btn { background: #2f5fe0; color: white; padding: 8px 14px; border: none; }
}
@layer utilities {
  .bg-green { background: #2f9e44; }
}
HTML
<div id="main"><button class="btn bg-green">Utilities win</button></div>
Result

#main .btn has far higher specificity than .bg-green, yet the button is green: the utilities layer is declared after components, and later layers win before specificity is even compared.

  • The first @layer statement fixes the order of the layers.
  • Styles not in any layer beat all layered styles.
  • Layers are excellent for keeping third-party CSS (put it in an early layer) from overpowering your own.

A simple file structure#

Text
styles/
├── tokens.css        /* custom properties: colours, spacing, fonts */
├── reset.css         /* a small reset and box-sizing */
├── base.css          /* element defaults: body, headings, links */
├── layout.css        /* page-level layout: header, grid, footer */
├── components/
│   ├── button.css
│   ├── card.css
│   └── nav.css
└── utilities.css     /* a few helpers: visually-hidden, text-center */

The order goes from general to specific, which matches how the cascade works. With @layer, you can make that order explicit.

Tools#

  • Stylelint checks CSS for errors and enforces conventions (naming, property order, no !important).
  • Prettier formats CSS consistently.
  • Browser developer tools show unused CSS in the Coverage panel (Chrome and Edge).

Practice#

Practice exercise

Rename the classes in this snippet using BEM, for a "profile" block with an avatar, a name and a "compact" variant:

HTML
<div class="profile small">
  <img class="pic" src="avatar.png" alt="">
  <h3 class="profile-name">Asha</h3>
</div>
Show solution
HTML
<div class="profile profile--compact">
  <img class="profile__avatar" src="avatar.png" alt="">
  <h3 class="profile__name">Asha</h3>
</div>
CSS
.profile { display: flex; gap: 12px; align-items: center; }
.profile__avatar { width: 64px; height: 64px; border-radius: 50%; }
.profile__name { margin: 0; }
.profile--compact .profile__avatar { width: 32px; height: 32px; }

Key takeaway#

Finished this lesson?