Skip to content
intermediate Phase 3 · CSS Advanced

CSS Architecture & Methodologies

Organize CSS with BEM, utility-first (Tailwind), and component-based approaches.

1h
0 problems
Topic Progress 0%

BEM Naming Convention

Block Element Modifier (BEM)

BEM is a naming methodology that provides a strict structure for class names, making CSS more predictable and maintainable. Every class follows the pattern block__element--modifier.

/* Block: a standalone component */
.card { border: 1px solid #e0e0e0; border-radius: 8px; }

/* Element: a part of the block */
.card__title { font-size: 1.5rem; font-weight: 700; padding: 16px; }
.card__body { padding: 0 16px 16px; line-height: 1.6; }
.card__image { width: 100%; height: 200px; object-fit: cover; }

/* Modifier: a variation of the block or element */
.card--featured { border-color: #1a73e8; box-shadow: 0 2px 8px rgba(26,115,232,0.2); }
.card__title--large { font-size: 2rem; }

Why BEM Matters

Without a methodology, CSS devolves into a soup of globally-scoped selectors. Two developers independently naming a button component might both choose .button, or worse, one uses .btn while the other uses .button-primary. BEM eliminates this ambiguity by enforcing a consistent naming contract.

BEM in HTML

<article class="card card--featured">
  <img class="card__image" src="/hero.jpg" alt="Hero">
  <h2 class="card__title card__title--large">Featured Post</h2>
  <div class="card__body">
    <p class="card__text">This post is highlighted on the homepage.</p>
    <a class="card__link card__link--cta" href="/read">Read more</a>
  </div>
</article>

Common BEM Pitfalls

  • Nesting too deep: Avoid block__element__element. If you need deeper nesting, the component should be split into smaller blocks.
  • Ignoring semantic meaning: BEM names should describe purpose, not appearance. Use card__title instead of card__big-text.
  • Overusing modifiers: If a modifier changes more than 30% of the block, consider creating a new block instead.

Utility-First with Tailwind CSS

Utility-First CSS

Utility-first frameworks like Tailwind CSS provide small, single-purpose classes that compose directly in HTML. Instead of writing custom CSS per component, you build designs by combining utilities.

<!-- Tailwind utility classes compose to form a card -->
<div class="max-w-sm rounded-lg border border-gray-200 bg-white shadow-md">
  <img class="w-full h-48 object-cover rounded-t-lg" src="/photo.jpg" alt="">
  <div class="p-5">
    <h3 class="mb-2 text-2xl font-bold text-gray-900">Card Title</h3>
    <p class="mb-3 text-base text-gray-600 leading-relaxed">
      Description text with utility classes for typography and spacing.
    </p>
    <a href="/" class="inline-block px-4 py-2 text-sm font-semibold text-white bg-blue-600 rounded hover:bg-blue-700 transition-colors">
      Action Button
    </a>
  </div>
</div>

Tailwind Configuration

Tailwind is configured via tailwind.config.js, where you extend the default design system:

// tailwind.config.js
module.exports = {
  content: ['./src/**/*.{html,js,jsx,ts,tsx}'],
  theme: {
    extend: {
      colors: {
        brand: {
          50: '#f0f4ff',
          500: '#3b82f6',
          900: '#1e3a5f',
        },
      },
      fontFamily: {
        sans: ['Inter', 'system-ui', 'sans-serif'],
      },
    },
  },
  plugins: [],
}

When to Extract Components

Utility-first does not mean writing every class inline forever. As patterns emerge, extract them into reusable components:

// React component extracting Tailwind utilities
function Button({ children, variant = 'primary', ...props }) {
  const base = 'inline-block px-4 py-2 text-sm font-semibold rounded transition-colors';
  const variants = {
    primary: 'bg-blue-600 text-white hover:bg-blue-700',
    secondary: 'bg-gray-200 text-gray-800 hover:bg-gray-300',
    danger: 'bg-red-600 text-white hover:bg-red-700',
  };
  return (
    <button className={`${base} ${variants[variant]}`} {...props}>
      {children}
    </button>
  );
}

CSS Modules and Scoped Styling

CSS Modules

CSS Modules generate unique class names at build time, giving you locally-scoped styles by default. When you import a .module.css file, the class names are transformed into hash-based strings.

/* Button.module.css */
.button {
  padding: 8px 16px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-weight: 600;
}

.primary {
  background-color: #3b82f6;
  color: white;
}

.primary:hover {
  background-color: #2563eb;
}

.outline {
  background-color: transparent;
  border: 2px solid #3b82f6;
  color: #3b82f6;
}

.outline:hover {
  background-color: #3b82f6;
  color: white;
}
// Button.jsx — classes are scoped automatically
import styles from './Button.module.css';

function Button({ variant = 'primary', children }) {
  return (
    <button className={`${styles.button} ${styles[variant]}`}>
      {children}
    </button>
  );
}

// Output: <button class="Button_button_a1b2c Button_primary_d3e4f">Click</button>

Global Styles in CSS Modules

Sometimes you need to break out of scoping. CSS Modules supports :global() for this:

/* Global reset that escapes module scope */
:global(body) {
  margin: 0;
  font-family: sans-serif;
}

/* Compose local class with global utility */
.heading {
  composes: title from './typography.module.css';
  color: var(--text-primary);
}

CSS Modules vs BEM vs Tailwind

Approach Scope Learning Curve Best For
BEM Manual naming convention Low Legacy projects, teams familiar with CSS
Tailwind Utility classes in HTML Medium Rapid prototyping, design-system-heavy apps
CSS Modules Build-time local scope Medium React/Vue apps, component libraries

Component-Based CSS Architecture

Component-Based CSS

Component-based CSS treats every UI element as an independent, self-contained unit with its own styles, structure, and behavior. This mirrors how modern frameworks like React, Vue, and Svelte organize code.

File Structure

src/
  components/
    Button/
      Button.jsx
      Button.module.css
      Button.test.js
    Card/
      Card.jsx
      Card.module.css
      CardHeader.jsx
      CardBody.jsx
    Modal/
      Modal.jsx
      Modal.module.css
      useModal.js
  styles/
    global.css          /* Reset, variables, base typography */
    utilities.css       /* Reusable utility classes */
    animations.css      /* Shared keyframe animations */

Design Tokens

Use CSS custom properties as a single source of truth for your design system:

/* global.css */
:root {
  /* Colors */
  --color-primary: #3b82f6;
  --color-primary-dark: #2563eb;
  --color-surface: #ffffff;
  --color-text: #1f2937;
  --color-text-muted: #6b7280;

  /* Spacing scale */
  --space-xs: 4px;
  --space-sm: 8px;
  --space-md: 16px;
  --space-lg: 24px;
  --space-xl: 32px;
  --space-2xl: 48px;

  /* Typography */
  --font-size-sm: 0.875rem;
  --font-size-base: 1rem;
  --font-size-lg: 1.25rem;
  --font-size-xl: 1.5rem;

  /* Shadows */
  --shadow-sm: 0 1px 2px rgba(0,0,0,0.05);
  --shadow-md: 0 4px 6px rgba(0,0,0,0.1);
  --shadow-lg: 0 10px 15px rgba(0,0,0,0.1);
}

Composition Over Inheritance

Favor composing small, reusable style fragments over creating deep inheritance chains:

/* Shared mixins via @apply or composes */
.flex-center {
  display: flex;
  align-items: center;
  justify-content: center;
}

.truncate {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Compose in components */
.card__header {
  composes: flex-center from './utilities.css';
  padding: var(--space-md);
  border-bottom: 1px solid #e5e7eb;
}

Scaling Architecture

As a codebase grows, introduce a "layers" pattern:

  1. Base layer — resets, CSS variables, global typography
  2. Component layer — individual component styles (BEM, CSS Modules, or Tailwind)
  3. Utility layer — small helper classes for one-off layouts
  4. Override layer — minimal page-specific adjustments (use sparingly)

This layering ensures specificity stays manageable and new styles never accidentally break existing components.

Quiz

1. In BEM methodology, what does the double dash `--` represent?

Question 1 options

2. What is the primary advantage of CSS Modules over BEM naming?

Question 2 options

3. Which CSS architecture approach is best for a team rapidly prototyping multiple UI variations without writing custom CSS?

Question 3 options

Flashcards

Question

What is BEM and what pattern do its class names follow?

Answer

BEM stands for Block Element Modifier. Class names follow the pattern `block__element--modifier`. A block is a standalone component, an element is a part of the block, and a modifier is a variation that changes appearance or behavior.

Question

How do CSS Modules achieve style isolation?

Answer

CSS Modules transform class names into unique hash-based strings at build time. When you import a `.module.css` file, each class is scoped to the component that imports it, preventing global namespace collisions without requiring manual naming conventions.

Question

What are design tokens in CSS architecture?

Answer

Design tokens are named values stored as CSS custom properties (e.g., `--color-primary`, `--space-md`) that represent the visual primitives of a design system. They act as a single source of truth, ensuring consistency across all components and enabling theme switching.

Revision Notes

Key Takeaways

  • 1. BEM provides a strict naming convention (`block__element--modifier`) that prevents class name collisions and makes CSS predictable, but requires team discipline to maintain
  • 2. Utility-first frameworks like Tailwind let you build UI rapidly in markup by composing small classes, but should be paired with component extraction once patterns stabilize
  • 3. CSS Modules offer build-time scoped class names automatically, eliminating the need for manual naming conventions in component-based frameworks
  • 4. A scalable CSS architecture uses layers: base (reset, variables), components (scoped styles), utilities (helpers), and overrides (minimal adjustments)

Interview Tips

  • When asked about CSS architecture, explain the tradeoffs between BEM (manual convention), Tailwind (utility-first), and CSS Modules (build-time scoping) rather than picking one as universally best
  • Be prepared to describe how you would structure a CSS codebase for a large team: design tokens, component-level scoping, import patterns, and a style guide
  • Discuss specific pain points CSS architecture solves: global namespace pollution, specificity wars, style duplication, and difficulty refactoring legacy styles
  • Mention real-world experience with tooling: PostCSS for Tailwind, webpack/vite loaders for CSS Modules, stylelint for enforcing conventions

Cheat Sheet

BEM: block__element--modifier — block = component, element = part, modifier = variation. Tailwind: compose utilities in HTML, extract into components when patterns emerge. CSS Modules: import styles object, use styles.className for scoped styles. Design tokens: CSS custom properties in :root for colors, spacing, typography. Architecture layers: base → components → utilities → overrides.