Skip to main content
Saved
Guide
Category strategy
Difficulty Intermediate

Choosing a CSS Strategy

Decision framework for selecting between BEM, CSS-in-JS, Utility-first, and CSS Modules.

Den Odell
By Den Odell Added

Choosing a CSS Strategy

How you write CSS affects whether your team ships features efficiently or struggles with specificity conflicts. This guide helps you evaluate which approach fits your situation.

Assess Your Situation First

Before adopting a CSS strategy, consider your context:

  • Team structure: When design and engineering teams collaborate closely, BEM works well. When teams work more independently, CSS Modules or stricter isolation prevents unintended style conflicts.
  • Build tooling: Modern bundlers (Vite, Next, Remix) make CSS Modules straightforward to implement. For serverless or custom rendering setups, evaluate CSS-in-JS performance costs carefully, especially for hydration timing.
  • Design system maturity: With an established design system and tokens, Utility-First CSS or CSS-in-JS work well. Without standardized design tokens, a simple naming convention can prevent inconsistency while the design system matures.
  • Performance requirements: CSS Modules and Utility-first approaches ship only the styles you use. CSS-in-JS can add runtime overhead that affects Core Web Vitals.

Evaluate the Core Options

BEM Class Naming

BEM Class Naming uses a structured naming system (Block__Element—Modifier) for predictable, globally scoped CSS. The class names can be verbose, but the naming convention provides clarity and prevents conflicts.

Choose BEM when:

  • You need CSS to work without JavaScript (progressive enhancement remains valuable)
  • Multiple teams collaborate on shared UI components
  • Designers already think in terms of reusable blocks and variations

Keep it manageable: BEM without governance can produce deeply nested class names like .header__nav__list__item__link--active--hover--mobile. Combine it with Design Tokens and Folder Organization to maintain clarity as stylesheets grow.

CSS Modules & Scoped Styles

CSS Modules automatically scope class names to individual components, preventing style conflicts. It provides scoped styles without framework lock-in.

Choose CSS Modules when:

  • You want to write standard CSS without worrying about class name conflicts
  • You’re building component libraries used across multiple projects (global styles and reusability often conflict)
  • Your build tool supports CSS module imports (import styles from "./button.module.css"), which most modern bundlers do

Extra protection: Use Style Isolation to keep third-party widget styles from bleeding into your components. Embedded widgets sometimes ship with global CSS that may override your styles.

Utility-First CSS

Utility-First CSS uses small, single-purpose classes (like p-4 or text-blue-500) to build UIs quickly. Developers tend to love or hate this approach.

Choose Utility-First when:

  • Teams build many UI variations and want to avoid debates over class names
  • Your design system already uses tokenized spacing and colors (without tokens, inconsistency accumulates quickly)
  • You want rapid iteration with automatic unused-code removal

Prevent chaos: Without discipline, class lists grow unwieldy: <div class="mt-4 md:mt-6 lg:mt-8 xl:mt-10 2xl:mt-12 hover:mt-3 focus:mt-5">. Use linting and Design Token governance to keep markup readable.

CSS-in-JS

CSS-in-JS writes styles directly in JavaScript, enabling dynamic styling based on props, themes, or state. It solves real problems but requires careful implementation.

Choose CSS-in-JS when:

  • Components need truly dynamic styles based on themes, user preferences, or runtime data (beyond just colocating styles with components)
  • Your SSR setup supports zero-runtime CSS extraction, or you’ve accepted the performance trade-off
  • You already use a Theme Provider and need programmatic access to design tokens

Performance tip: Styles generated from props recalculate on every render. Memoize and cache dynamic styles, especially in large lists where overhead adds up.

Combine Strategies When It Makes Sense

In my experience, you don’t have to stick to one approach.

  • Foundation + Layout: Use BEM or CSS Modules for design system components that remain stable, then add Utility-first classes for page layouts that change frequently.
  • Static + Dynamic: Keep most components in CSS Modules but use CSS-in-JS when you need dynamic theming or runtime-responsive styles. Not every component requires JavaScript for styling.
  • Always use design tokens: Regardless of your CSS strategy, maintain one source of truth for spacing, colors, and typography. Without this, similar values drift apart over time.

Quick Decision Guide

Your SituationBest StrategyReason
Refactoring existing global CSSBEM Class NamingIncrementally improve without rewriting your entire build pipeline
Building a shared component libraryCSS ModulesPrevents conflicts across projects without requiring consumers to adopt your framework
Rapid prototyping and iterationUtility-First CSSShip UI changes without debates over semantic class names
Dynamic theming or personalisationCSS-in-JSEnables runtime styling decisions (but measure the performance cost)

Next steps: Document your decision before multiple approaches emerge independently across the codebase. Update linting rules so the team follows the strategy. Add examples to your style guide. Review your choice periodically as teams and requirements change.

Newsletter

A Monthly Email
from Den Odell

Behind-the-scenes thinking on frontend patterns, site updates, and more

No spam. Unsubscribe anytime.