Episode 1 — Fundamentals / 1.10 — CSS Architecture and Project Structure

1.10.b — Component-Based CSS

In one sentence: Treat every piece of UI as a self-contained component with one job, a clear public API of classes, and styles that cannot leak into — or be leaked on by — the rest of the page.

Navigation: ← 1.10.a — BEM Naming Convention · 1.10.c — Utility vs Component Classes →


1. What "component" means in CSS

A component is a discrete, reusable chunk of UI. It could be a card, a button group, a modal, or a pricing table. In component-based CSS:

  • One component = one file (or one clearly separated block).
  • The component owns its internal styles — nothing outside should reach in.
  • Other code interacts with the component only through its public class names (the component API).

This mirrors how modern frameworks (React, Vue, Svelte) think about UI, but the idea predates all of them.


2. The component API

Every component has three layers:

LayerPurposeExample
Base classDefault look and structure.alert
ModifiersVisual / behavioural variants.alert--success, .alert--warning
StatesDynamic conditions (often JS-driven).alert.is-visible, .alert.is-dismissing
<div class="alert alert--success is-visible">
  <p class="alert__message">Settings saved.</p>
  <button class="alert__close">&times;</button>
</div>

Everything the consumer needs to know is visible in the class list — no hunting through nested selectors.


3. Single responsibility

A component should do one thing. If you find yourself adding layout logic (grid columns, page-level spacing) inside a component file, that is a sign you are mixing concerns.

❌  .card { width: 33.33%; float: left; padding: 1rem; }   /* card + grid mixed */

✅  .card { padding: 1rem; }                                /* card only */
    .grid__col--third { width: 33.33%; }                    /* layout separate */

The card does not know (or care) how it is placed on the page. The grid does not know what is inside each cell.


4. Composing components

Complex UI is built by nesting components, not by making mega-components:

<section class="pricing">
  <div class="pricing__tier">
    <div class="card card--highlighted">
      <h3 class="card__title">Pro Plan</h3>
      <ul class="feature-list">
        <li class="feature-list__item">Unlimited projects</li>
        <li class="feature-list__item">Priority support</li>
      </ul>
      <button class="btn btn--primary">Choose Plan</button>
    </div>
  </div>
</section>

.pricing, .card, .feature-list, and .btn are four independent components. Each can be developed, tested, and reused in isolation.


5. Avoiding style leakage

Style leakage = styles from one component accidentally affecting another. Common causes:

CauseExampleFix
Tag selectors.card h2 { … } overrides headings everywhere insideUse .card__title instead
Descendant combinators.sidebar .link { … } catches links in nested componentsScope to direct children or use BEM
Global resets gone wrong* { box-sizing: … } is fine; ul { list-style: none; } is notApply resets explicitly to component elements
Specificity escalationAdding #id or !important to "win"Stay at single-class specificity

The golden rule

A component's styles should only target its own class names — never bare tags, IDs, or classes belonging to other components.


6. Scoping strategies

StrategyHow it worksWhen to use
BEM namingConvention-based; relies on developer disciplineMulti-page sites, no build tool needed
CSS ModulesBuild tool rewrites .card.card_a3f2x; imports produce a map objectReact / framework projects with bundlers
Scoped stylesVue <style scoped> auto-adds [data-v-xxxx] attribute selectorsVue single-file components
Shadow DOMTrue browser-level encapsulation; external styles cannot enterWeb Components; strictest isolation
CSS-in-JSStyles generated in JS, hashed at runtime or build timeStyled-components, Emotion, etc.

All strategies solve the same problem — preventing leakage — with different trade-offs in tooling, runtime cost, and developer experience.


7. Component documentation (style guides)

For teams, document each component:

## Alert

Base: `.alert`
Modifiers: `--success`, `--warning`, `--error`, `--info`
States: `.is-visible`, `.is-dismissing`
Elements: `__message`, `__close`

Usage:
  <div class="alert alert--success is-visible"></div>

Tools like Storybook automate this, but even a simple markdown file in /docs/ prevents "what class do I use?" questions.


8. Key takeaways

  1. One component = one file, one job. Keep layout out.
  2. The public API is base class + modifiers + states — consumers should never need to know the internal DOM.
  3. Never style bare tags inside a component — always use scoped class names.
  4. Compose complex UI from small, independent components.
  5. Pick a scoping strategy (BEM, CSS Modules, scoped styles) and enforce it consistently.

Explain-It Challenge

Explain without notes:

  1. Why should a .card component not set its own width or margin?
  2. What is style leakage, and name two CSS patterns that cause it.
  3. Compare BEM scoping (convention) with CSS Modules (tooling) — give one advantage of each.

Navigation: ← 1.10.a — BEM Naming Convention · 1.10.c — Utility vs Component Classes →