Skip to main content

πŸ›οΈ 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 .button rule 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.

graph TD A[CSS Architecture Methodologies] --> B[BEM] A --> C[SMACSS] A --> D[OOCSS] B --> E["Naming: Block__Element--Modifier"] C --> F["Categorizing: Base Β· Layout Β· Module Β· State Β· Theme"] D --> G["Reuse: Structure vs Skin, Container vs Content"]

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.

The three parts of a BEM class name A Block is a standalone component; an Element is a part of a block joined with two underscores; a Modifier is a variation joined with two hyphens. Block .card A standalone, meaningful component Element .card__title A part of the block β€” two underscores Modifier .card--featured A variation of a block or element β€” two hyphens
Figure 1 β€” BEM encodes structure in the name itself: 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.

graph TD A[SMACSS Categories] --> B["Base β€” element defaults"] A --> C["Layout β€” major page regions (l-)"] A --> D["Module β€” reusable components"] A --> E["State β€” is-/has- variations"] A --> F["Theme β€” color & skin overrides"]

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.

The two OOCSS separations Separate structure classes from skin classes, and separate a component's styling from its container so it works anywhere. Structure vs. Skin .btn β€” layout, padding, shape the reusable skeleton .btn-blue β€” color, border the swappable paint job Container vs. Content .heading β€” same everywhere styled by class, not location not .sidebar h3 { … } avoid context-dependent rules
Figure 2 β€” OOCSS's two separations both push toward context-independent, reusable pieces.

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.

FeatureBEMSMACSSOOCSS
Primary focusNaming conventionCategorizing rulesReuse principles
Answers"What do I name it?""Where does it go?""How do I reuse it?"
HTML impactLong, descriptive namesModerate, category-basedMany small classes
Best forComponent-heavy appsLarge, structured sitesSites 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

  1. Rename everything into a single stat-card block with BEM elements and modifiers.
  2. Replace .stat-value, .stat-label, .stat-change with __element names.
  3. Replace the .positive / .negative state selectors with BEM --modifier names.
  4. Swap the hard-coded colors for the theme variables (var(--success-border), var(--warning-border), var(--card-bg)).
  5. 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.