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:
| Layer | Purpose | Example |
|---|---|---|
| Base class | Default look and structure | .alert |
| Modifiers | Visual / behavioural variants | .alert--success, .alert--warning |
| States | Dynamic 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">×</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:
| Cause | Example | Fix |
|---|---|---|
| Tag selectors | .card h2 { … } overrides headings everywhere inside | Use .card__title instead |
| Descendant combinators | .sidebar .link { … } catches links in nested components | Scope to direct children or use BEM |
| Global resets gone wrong | * { box-sizing: … } is fine; ul { list-style: none; } is not | Apply resets explicitly to component elements |
| Specificity escalation | Adding #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
| Strategy | How it works | When to use |
|---|---|---|
| BEM naming | Convention-based; relies on developer discipline | Multi-page sites, no build tool needed |
| CSS Modules | Build tool rewrites .card → .card_a3f2x; imports produce a map object | React / framework projects with bundlers |
| Scoped styles | Vue <style scoped> auto-adds [data-v-xxxx] attribute selectors | Vue single-file components |
| Shadow DOM | True browser-level encapsulation; external styles cannot enter | Web Components; strictest isolation |
| CSS-in-JS | Styles generated in JS, hashed at runtime or build time | Styled-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
- One component = one file, one job. Keep layout out.
- The public API is base class + modifiers + states — consumers should never need to know the internal DOM.
- Never style bare tags inside a component — always use scoped class names.
- Compose complex UI from small, independent components.
- Pick a scoping strategy (BEM, CSS Modules, scoped styles) and enforce it consistently.
Explain-It Challenge
Explain without notes:
- Why should a
.cardcomponent not set its ownwidthormargin? - What is style leakage, and name two CSS patterns that cause it.
- 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 →