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.
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 aneeds 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:
.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; }<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>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:
<div class="p-3 border rounded bg-light">
<h3 class="m-0 text-lg">HTML course</h3>
</div>.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:
@layer reset, base, components, utilities;
@layer components {
#main .btn { background: #2f5fe0; color: white; padding: 8px 14px; border: none; }
}
@layer utilities {
.bg-green { background: #2f9e44; }
}<div id="main"><button class="btn bg-green">Utilities win</button></div>#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
@layerstatement 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#
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:
<div class="profile small">
<img class="pic" src="avatar.png" alt="">
<h3 class="profile-name">Asha</h3>
</div>Show solution
<div class="profile profile--compact">
<img class="profile__avatar" src="avatar.png" alt="">
<h3 class="profile__name">Asha</h3>
</div>.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?