CSS architecture: BEM, ITCSS, and utility-class hybrids. The goal is organised stylesheets that another developer can navigate, not a holy war about naming. Pick patterns that fit the project size and team, then stick to them.
Why architecture matters
Small sites survive one styles.css. Real projects accumulate components, overrides, third-party CSS, and six months of ‘just this once’ rules. Without structure, specificity creeps, duplicate styles appear, and nobody knows where to add the next button variant.
BEM – Block Element Modifier
BEM names describe component structure:
- Block – standalone component (
.card) - Element – part of a block (
.card__title) - Modifier – variant or state (
.card--featured,.card__title--large)
.card { … }
.card__media { … }
.card__body { … }
.card__title { … }
.card--horizontal { … }
.card--featured { … }
Class names get long. In return, you avoid deep nesting in selectors and know exactly what a rule affects. Pair with single-class specificity or :where() on the block wrapper if overrides should stay easy.
ITCSS – inverted triangle layers
ITCSS orders CSS from low to high specificity, generic to explicit:
- Settings – variables, design tokens
- Tools – mixins, functions (SCSS layer)
- Generic – reset, box-sizing
- Elements – bare tags (
a,h1) - Objects – layout patterns (
.o-layout) - Components – UI pieces (
.c-card) - Utilities – single-purpose helpers (
.u-text-center)
Maps neatly to @layer in plain CSS. File folders often mirror the layers: settings/, components/, utilities/.
Utility classes
Utilities do one job: .mt-4, .flex, .sr-only. Tailwind popularised the approach; you can write a small utility set without adopting a framework:
@layer utilities {
.flex { display: flex; }
.gap-4 { gap: 1rem; }
.items-center { align-items: center; }
.text-center { text-align: center; }
.sr-only {
position: absolute;
inline-size: 1px;
block-size: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
}
Utilities shine for one-off layout tweaks in templates. They become painful when every element carries twelve classes and the HTML is unreadable.
A sensible hybrid
Most Codeskill-sized projects do well with:
- Tokens on
:root(settings) - BEM-ish components for repeated UI
- A thin utility layer for spacing and visibility helpers
@layerto enforce order
@layer reset, base, layout, components, utilities;
@layer components {
.site-header { … }
.button { … }
.card { … }
}
@layer utilities {
.stack > * + * {
margin-block-start: var(--space-stack);
}
}
Naming conventions that help
Whatever system you pick, be consistent:
- Prefix layout objects if you use them (
l-oro-) - Prefix components if multiple authors (
c-) or skip prefixes on small sites - State classes:
.is-open,.is-active, or data attributes ([aria-expanded="true"]) styled directly - JavaScript hooks:
js-toggle-menu– never style these; JS and CSS decouple
What to avoid
- Deep nesting in SCSS that compiles to high-specificity selectors
- Global element rules that leak into components (
div { … }) - !important as default conflict resolution
- Rewriting architecture mid-project without a migration plan
Document the decision
A short README in your CSS folder beats oral tradition: layer order, naming examples, where new components live, when a utility is OK vs when to create a component class. Future you is another developer.
The next tutorial covers working with SCSS output sanely – reading compiled CSS, source maps, and hand-off from the SCSS tutorials.

