Module 8: Advanced CSS & Architecture
CSS Methodologies: BEM, OOCSS and SMACSS
Why large stylesheets decay, and how three naming and organization systems keep specificity flat, styles predictable and components independent.
CSS has no scope. Every rule you write is global, and every selector can affect any element on any page. On a small site that is fine. On a project with ten developers and two years of history, it turns into a stylesheet where nobody dares to delete a rule and every change needs !important.
A methodology is a set of conventions that makes that sprawl manageable. None of them is magic. They are agreements about how to name things and where to put them.
What goes wrong without a system#
.sidebar .widget h3 { color: #333; }
.news .widget h3 { color: #0b5fff; }
.widget h3.special { color: red; }
.page .sidebar .widget h3.special { color: red !important; }
- Selectors depend on page structure, so moving a widget silently changes its style.
- Specificity climbs with every fix.
- You cannot tell what the styles apply to without reading the HTML.
All three methodologies below attack these problems.
BEM: Block, Element, Modifier#
BEM is a naming convention. The name of a class tells you what it is and how it relates to others.
- Block: a standalone component.
card,menu,search-form. - Element: a part of a block that has no meaning outside it. Written with two underscores:
card__title,menu__item. - Modifier: a variation or state of a block or element. Written with two hyphens:
card--featured,menu__item--active.
<article class="card card--featured">
<img class="card__image" src="..." alt="">
<h3 class="card__title">Title</h3>
<p class="card__text">Description</p>
<a class="card__button button button--primary" href="#">Read more</a>
</article>
.card { border: 1px solid #ddd; border-radius: 8px; padding: 1rem; }
.card--featured { border-color: #0b5fff; }
.card__title { font-size: 1.25rem; margin: 0 0 0.5rem; }
.card__text { color: #555; }
Every selector is a single class. Specificity is always (0,1,0), so source order decides and overrides are predictable. The class names make the HTML readable: you can see which parts belong to the card.
BEM rules that keep it clean#
- Elements do not nest in names. Write
card__title, notcard__body__title, even when the HTML is nested. The name describes membership in the block, not the DOM depth. Changing the markup should not require renaming classes. - A modifier is added alongside the base class, not instead of it:
class="button button--primary". - Never style an element without its block.
.card__titleis fine,.card h3is not. - Avoid styling by tag inside components.
Cost#
Class names get long. That is the trade: verbosity in exchange for predictability. It also works well with Sass, where &__title and &--featured generate the names for you.
OOCSS: Object-Oriented CSS#
OOCSS is a set of principles rather than a naming scheme.
1. Separate structure from skin. Layout and geometry in one class, colors and decoration in another.
/* structure */
.btn { display: inline-block; padding: 0.6em 1.2em; border-radius: 6px; }
/* skin */
.btn-primary { background: #0b5fff; color: #fff; }
.btn-danger { background: #b42318; color: #fff; }
2. Separate container from content. A component should look the same wherever it is placed. Do not write .sidebar .btn to restyle a button. If it needs to look different in the sidebar, create a variant, or let the placement context control only layout (spacing, width), not the component's appearance.
/* fragile: the button's appearance depends on where it is */
.sidebar .btn { font-size: 0.8rem; }
/* robust */
.btn--small { font-size: 0.8rem; }
The reward is reuse. A "media object" (an image beside text) is built once and used for comments, notifications and search results.
SMACSS: Scalable and Modular Architecture#
SMACSS organizes files and rules into five categories:
| Category | What it holds | Example |
|---|---|---|
| Base | element defaults, resets, no classes | body, a, h1 |
| Layout | the major page regions, prefixed l- | .l-header, .l-grid |
| Module | reusable components | .card, .nav |
| State | temporary conditions, prefixed is- or has- | .is-open, .is-hidden |
| Theme | visual skins that override other layers | .theme-dark |
/* Base */
body { font: 1rem/1.6 system-ui; }
/* Layout */
.l-container { max-width: 70rem; margin-inline: auto; }
/* Module */
.tabs { display: flex; }
.tabs__tab { padding: 0.5rem 1rem; }
/* State */
.tabs__tab.is-active { border-bottom: 2px solid #0b5fff; }
The prefixes tell a reader what kind of rule they are looking at. A class starting with is- is a JavaScript-toggled state, and you know not to reuse it as a permanent style.
Comparing them#
| BEM | OOCSS | SMACSS | |
|---|---|---|---|
| Main idea | naming | reusable objects | rule categories |
| Solves | unclear names, specificity | duplication, context-dependence | file and layer organization |
| Strict? | quite | principles | guidelines |
They combine well. A common modern setup is BEM names, an OOCSS mindset about reuse, and SMACSS-style folders (base, layout, components, utilities).
Layers add a safety net#
The @layer rule from the cascade lesson formalizes what SMACSS tried to achieve by convention:
@layer reset, base, layout, components, utilities;
You can keep the BEM naming and let layers guarantee that utilities always beat components, without increasing specificity.
Other approaches to scope#
Some teams skip methodology and let tools enforce scope instead:
- CSS Modules compile class names into unique ones per file, so collisions are impossible.
- Scoped styles in frameworks like Vue and Svelte do something similar.
- Utility-first CSS (Tailwind, next lesson) avoids naming altogether.
A methodology matters most when you write plain CSS in a shared codebase. If your framework already scopes styles per component, you need fewer naming rules, but the ideas of low specificity and reusable objects still apply.
Common mistakes#
- BEM with DOM-depth names like
card__body__title__icon. - Styling by tag or by ancestor inside a block.
- Mixing conventions in one project: half BEM, half descendant selectors.
- Putting layout in a component, such as a card with a fixed
width: 33%, so it cannot be reused elsewhere. Let a parent control the layout. - Using state classes for permanent styling.
- Over-engineering a small site with strict rules that cost more than they save.
- Not documenting the convention so each new developer invents their own.
Practice#
- Refactor the tangled CSS example at the start of this lesson into BEM classes with single-class selectors.
- Build a "media object" component (image plus text) and use it for three different contexts, without any context-specific selectors.
- Organize a small project's CSS into base, layout, components and utilities folders, and use
@layerto enforce the order. - Take a component that depends on its container (
.sidebar .btn), and make it independent using a modifier.