ποΈ CSS Organization Methodologies (BEM, SMACSS, OOCSS)
CSS has no built-in module system, so on a big project every class name lives in one giant global namespace. Methodologies are the naming and organizing conventions the community invented to bring order to that chaos. In this lesson you'll learn the three most influential ones β and, more importantly, when to reach for each.
π― Learning Objectives
By the end of this lesson, you will be able to:
- Explain the specific problems (specificity wars, naming collisions, duplication) that CSS methodologies solve
- Write component classes using the BEM Block__Element--Modifier convention
- Categorize rules into the five SMACSS buckets and organize files accordingly
- Apply the OOCSS principles of separating structure from skin, and container from content
- Choose and combine methodologies to fit a project's size and team
Estimated Time: 35β45 minutes β’ Difficulty: Intermediate
Hands-on: Refactor a messy dashboard stylesheet into clean BEM components.
In This Lesson
Why CSS Needs Architecture
A small stylesheet is easy: you write a few selectors and everything works. But CSS was designed for documents, not for the sprawling component-based apps we build today. As a project grows past a few hundred lines, the language's own features start to work against you:
- The global namespace β every selector you write is visible everywhere. Two developers who both add a
.buttonrule will silently clobber each other. - Specificity conflicts β when a style won't apply, the tempting fix is a more specific selector, which starts an escalating "specificity war" that ends in
!important. - Duplication β without shared conventions, the same visual pattern gets rewritten in five slightly different ways.
- Fear of deletion β nobody dares remove a rule because they can't tell what it still affects. The file only ever grows.
π‘ A useful analogy: A methodology is like a building code. It doesn't change what bricks and beams can do β it gives everyone on the crew a shared, predictable way to assemble them so the structure stays sound as it grows and as new people join.
None of the three approaches below is a framework or a tool you install. They are conventions β agreements about how you name classes and where you put rules. That's exactly why they're valuable: they cost nothing but discipline, and they pay off most on the projects that need them.
BEM β Block, Element, Modifier
BEM, developed at the Russian search company Yandex, is first and foremost a naming convention. It answers one question: what should I name this class? The answer is a strict pattern that makes the relationship between a component and its parts visible right in the markup.
block__element--modifier.Block
A block is a standalone component that means something on its own and could be moved anywhere on the page. Blocks are your top-level building pieces.
/* Blocks β independent, reusable components */
.header { }
.menu { }
.search-form { }
.button { }
Element
An element is a part of a block that has no meaning outside of it β like the legs and seat of a chair. Elements are joined to the block name with two underscores.
/* Elements β belong to a block, joined with __ */
.menu__item { }
.search-form__input { }
.button__icon { }
β οΈ Keep names flat, not nested
Even when the HTML nests deeply, BEM element names stay two levels deep. Write .card__title, not .card__header__title. The class describes which block it belongs to, not the full DOM path β that keeps names short and lets you rearrange markup freely.
Modifier
A modifier is a flag that changes a block or element's appearance, state, or behavior. It's joined with two hyphens, and it's always applied alongside the base class, never instead of it.
/* Modifiers β variations, joined with -- */
.button--primary { }
.button--large { }
.menu__item--active { }
BEM in Practice
Here is a card component written end to end. Notice how you can read the structure of the HTML from the class names alone, without seeing the markup nesting.
<article class="card card--featured">
<header class="card__header">
<h2 class="card__title">Article Title</h2>
<p class="card__meta">Posted January 1, 2026</p>
</header>
<div class="card__content">
<p>Article content goes hereβ¦</p>
</div>
<footer class="card__footer">
<button class="card__button card__button--primary">Read More</button>
<button class="card__button card__button--secondary">Save</button>
</footer>
</article>
/* Block */
.card {
background-color: var(--card-bg);
border-radius: 8px;
box-shadow: 0 2px 4px rgb(0 0 0 / 0.1);
padding: 20px;
}
/* Block modifier */
.card--featured {
border-left: 4px solid var(--primary-color);
box-shadow: 0 4px 8px rgb(0 0 0 / 0.2);
}
/* Elements */
.card__header { margin-bottom: 15px; border-bottom: 1px solid var(--border-color); }
.card__title { margin: 0 0 5px; font-size: 1.5rem; }
.card__meta { color: var(--text-light); font-size: 0.875rem; }
.card__footer { display: flex; justify-content: flex-end; gap: 10px; }
.card__button {
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
}
/* Element modifiers */
.card__button--primary { background-color: var(--primary-color); color: #fff; }
.card__button--secondary { background-color: transparent; color: var(--primary-color); border: 1px solid currentColor; }
β Why BEM works
Almost every selector is a single class, so they all have the same low specificity β no specificity wars. The names are self-documenting, blocks are reusable, and you can move a block anywhere without its styles breaking. The cost is verbosity: names get long, and elements can feel repetitive.
SMACSS β Categorizing Your Rules
SMACSS (Scalable and Modular Architecture for CSS), created by Jonathan Snook, is less about naming and more about sorting. It says: every CSS rule you write falls into one of five categories, and if you file each rule under the right category, your stylesheet stays navigable no matter how big it gets.
1. Base
Defaults applied directly to elements β no classes. This is your reset and your baseline typography.
body { margin: 0; }
a { color: var(--primary-color); text-decoration: none; }
a:hover { text-decoration: underline; }
2. Layout (prefix l-)
The major structural regions of a page β header, sidebar, main, footer, grid. SMACSS suggests an l- prefix so layout rules are easy to spot.
.l-header { position: sticky; top: 0; z-index: 100; }
.l-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 20px;
}
3. Module
The reusable components that live inside layouts β cards, buttons, navs. This is where the bulk of your CSS lives. Child parts get module-prefixed names (.card-title, not a bare .title) to stay scoped.
.card { }
.card-title { }
.btn { }
.btn-primary { }
4. State (prefix is- / has-)
How a module looks in a particular state, usually toggled by JavaScript. States are meant to override, so they read clearly: is-active, is-hidden, has-error.
.is-hidden { display: none; }
.nav-item.is-active { font-weight: 700; }
.field.has-error { border-color: var(--warning-border); }
5. Theme
Optional. Color schemes and skins that can be swapped β most naturally implemented today with CSS custom properties (the subject of the next lesson).
.theme-dark {
--bg: #121212;
--text: #f8f8f8;
--accent: #bb86fc;
}
π The payoff: file organization
SMACSS's categories map directly onto folders, so any developer knows exactly where a rule lives:
css/
βββ base/ reset.css, typography.css
βββ layout/ grid.css, header.css, footer.css
βββ modules/ nav.css, card.css, button.css
βββ state/ states.css
βββ theme/ dark.css, light.css
βββ main.css imports all of the above
SMACSS is more flexible and less prescriptive than BEM, which is its strength and its weakness: you get freedom, but you also have to make more judgment calls about which bucket a rule belongs in. Many teams pair the two β SMACSS categories for organization, BEM naming inside the module bucket.
OOCSS β Structure vs. Skin
OOCSS (Object-Oriented CSS), pioneered by Nicole Sullivan, is a set of principles rather than a naming scheme. Its goal is maximum reuse, achieved through two rules of separation.
1. Separate structure from skin
Split the properties that define shape (display, padding, dimensions) from the ones that define look (color, border, background). Now one structural class can wear many skins.
/* β Structure and skin welded together β duplicated for every color */
.button-green {
display: inline-block;
padding: 5px 15px;
border-radius: 3px;
background-color: green;
color: white;
}
/* β
Structure once, skins as needed */
.btn { display: inline-block; padding: 5px 15px; border-radius: 3px; }
.btn-green { background-color: green; color: white; }
.btn-blue { background-color: blue; color: white; }
2. Separate container from content
Style an element by its own class, never by where it happens to sit in the DOM. A heading should look the same whether it's in the sidebar or the footer.
/* β Location-dependent β the same heading is defined twice */
.sidebar h3 { font-size: 16px; color: #333; }
.footer h3 { font-size: 16px; color: #fff; }
/* β
Class-based β the object is portable */
.heading { font-size: 16px; line-height: 1.2; }
.sidebar { color: #333; }
.footer { color: #fff; }
Followed to its conclusion, OOCSS produces small single-purpose classes you compose in the HTML β which is exactly the idea behind utility frameworks like Tailwind and the reason Bootstrap's .btn / .btn-primary split looks the way it does.
<button class="btn bg-primary text-light shadow-sm">Follow</button>
π‘ The trade-off
OOCSS gives you tiny, DRY, endlessly recombinable styles β at the cost of class-heavy HTML and less semantic markup. You gain flexibility and a smaller stylesheet; you lose the "read the meaning from the class" quality that BEM prizes.
Comparing & Combining Them
These three aren't rivals β they solve different problems and layer together naturally.
| Feature | BEM | SMACSS | OOCSS |
|---|---|---|---|
| Primary focus | Naming convention | Categorizing rules | Reuse principles |
| Answers | "What do I name it?" | "Where does it go?" | "How do I reuse it?" |
| HTML impact | Long, descriptive names | Moderate, category-based | Many small classes |
| Best for | Component-heavy apps | Large, structured sites | Sites with many UI variations |
The hybrid most teams actually use
In practice you rarely pick just one. A common, effective blend: organize files with SMACSS categories, name your components with BEM, and add a layer of OOCSS-style utility classes for one-off tweaks.
/* Base (SMACSS) */
a { color: var(--primary-color); }
/* Layout (SMACSS naming) */
.l-header { position: sticky; top: 0; }
/* Module with BEM naming */
.card { padding: 20px; }
.card__title { font-size: 1.5rem; }
/* Utility / skin classes (OOCSS) */
.text-large { font-size: 1.25rem; }
.mt-4 { margin-top: 1rem; }
β The one rule above all rules
The best methodology is the one your team applies consistently. A simple convention everyone follows beats a sophisticated one applied halfway. Pick an approach, write it down, and enforce it in code review.
Hands-on Exercise
ποΈ Refactor a Dashboard Stat Card into BEM
Objective: Turn context-dependent, ambiguous CSS into a clean, reusable BEM block.
Here is a slice of an unstructured dashboard stylesheet. The .stat-card uses bare state classes like .positive and .negative that could collide with anything, and a compound .stat-card.negative selector.
/* Before β unstructured */
.stat-card { background: #fff; border-radius: 8px; padding: 20px; }
.stat-card.negative { border-left: 4px solid #f44336; }
.stat-value { font-size: 28px; font-weight: bold; }
.stat-label { color: #777; font-size: 14px; }
.stat-change { font-size: 14px; font-weight: bold; }
.stat-change.positive { color: #4caf50; }
.stat-change.negative { color: #f44336; }
Your task
- Rename everything into a single
stat-cardblock with BEM elements and modifiers. - Replace
.stat-value,.stat-label,.stat-changewith__elementnames. - Replace the
.positive/.negativestate selectors with BEM--modifiernames. - Swap the hard-coded colors for the theme variables (
var(--success-border),var(--warning-border),var(--card-bg)). - Write the matching HTML for one positive and one negative card.
π‘ Hint
The block is stat-card. Its parts become stat-card__value, stat-card__label, stat-card__change. Trend variations become stat-card__change--up and stat-card__change--down; the whole-card warning state becomes stat-card--negative. Remember: modifiers are applied alongside the base class.
β Solution
/* After β BEM */
.stat-card {
background: var(--card-bg);
border-radius: 8px;
padding: 20px;
}
.stat-card--negative { border-left: 4px solid var(--warning-border); }
.stat-card__value { font-size: 28px; font-weight: 700; }
.stat-card__label { color: var(--text-light); font-size: 14px; }
.stat-card__change { font-size: 14px; font-weight: 700; }
.stat-card__change--up { color: var(--success-border); }
.stat-card__change--down { color: var(--warning-border); }
<div class="stat-card">
<div class="stat-card__value">$12,345</div>
<div class="stat-card__label">Revenue</div>
<div class="stat-card__change stat-card__change--up">+12.5%</div>
</div>
<div class="stat-card stat-card--negative">
<div class="stat-card__value">$5,432</div>
<div class="stat-card__label">Expenses</div>
<div class="stat-card__change stat-card__change--down">+2.7%</div>
</div>
Every selector is now a single class with the same specificity, the names are self-explanatory, and the card is portable to any page.
Best Practices
β Do
- Keep specificity low and flat β prefer single classes over descendant selectors.
- Document your chosen convention in the repo and check it in code review.
- Adopt a methodology on new components first; refactor legacy code gradually.
- Reach for a linter (like Stylelint with a BEM plugin) to enforce naming automatically.
β οΈ Don't
- Don't over-engineer a tiny project β a landing page doesn't need a five-folder architecture.
- Don't apply a methodology halfway; inconsistency is more confusing than no convention at all.
- Don't nest BEM elements (
block__el1__el2) β keep names two levels deep. - Don't be dogmatic. These are guidelines; bend them where your project genuinely benefits.
Summary & Quiz
π Key Takeaways
- CSS has one global namespace; methodologies are conventions that impose order on it.
- BEM = naming:
block__element--modifier, flat and low-specificity. - SMACSS = sorting rules into Base, Layout, Module, State, Theme.
- OOCSS = reuse: separate structure from skin, and container from content.
- Most real projects blend all three β and consistency matters more than the choice.
π― Quick Quiz
Question 1: In BEM, how would you name the "title" part of a "card" block?
Question 2: Which SMACSS category do classes like is-active and has-error belong to?
Question 3: What is the core idea of OOCSS's "separate structure from skin"?
π Further Reading
π What's Next?
You've seen SMACSS's "Theme" category and OOCSS's skin classes hint at swappable values. Next we'll make that concrete with CSS Custom Properties and Variables β the native, dynamic way to store design tokens and power theming.
π Well organized!
Your stylesheets can now scale without turning into a maintenance nightmare.