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 you adopt a CSS strategy, it’s worth thinking about your context:
- Team structure: If your design and engineering teams collaborate closely, BEM works well. If they work more independently, CSS Modules or stricter isolation prevents unintended style conflicts.
- Build tooling: Modern build tools and frameworks such as Vite and Next.js make CSS Modules straightforward to implement. For serverless or custom rendering setups, CSS-in-JS needs a careful look at its runtime cost, especially its effect on hydration timing.
- Design system maturity: If you already have an established design system and tokens, Utility-First CSS or CSS-in-JS work well. If you don’t have standardized design tokens yet, 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 convention makes it clear what each class belongs to and helps avoid conflicts.
Choose BEM when:
- You need CSS to work without JavaScript, because you’re building with progressive enhancement
- 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. If you pair it with Design Tokens and Folder Organization, your stylesheets stay readable as they grow.
CSS Modules & Scoped Styles
CSS Modules automatically scope class names to individual components, preventing style conflicts. They give you scoped styles without tying you to a framework.
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, where global styles get in the way of reuse
- Your build tool supports CSS module imports like
import styles from "./button.module.css", which most modern bundlers do
Extra protection: Style Isolation keeps 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, because without tokens inconsistency builds up quickly
- You want rapid iteration with automatic unused-code removal
Keep markup readable: Without discipline, class lists get very long: <div class="mt-4 md:mt-6 lg:mt-8 xl:mt-10 2xl:mt-12 hover:mt-3 focus:mt-5">. Linting and Design Token governance keep them under control.
CSS-in-JS
CSS-in-JS writes styles directly in JavaScript, enabling dynamic styling based on props or state. It handles dynamic styling well, but it needs careful implementation to keep runtime overhead down.
Choose CSS-in-JS when:
- Components need dynamic styles based on themes or runtime data, not 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 can be recalculated on every render. Memoizing and caching dynamic styles helps, especially in large lists where that work adds up.
Combine Strategies When It Makes Sense
In my experience, you don’t have to stick to one approach.
- Foundation + Layout: BEM or CSS Modules for design system components that stay stable, with Utility-first classes for page layouts that change frequently.
- Static + Dynamic: Most components stay in CSS Modules, and CSS-in-JS comes in only where you need dynamic theming or runtime-responsive styles. Not every component requires JavaScript for styling.
- Design tokens everywhere: Whatever CSS strategy you choose, it helps to keep one source of truth for values like spacing and color. Without one, similar values drift apart over time.
Quick Decision Guide
| Your Situation | Best Strategy | Reason |
|---|---|---|
| Refactoring existing global CSS | BEM Class Naming | Incrementally improve without rewriting your entire build pipeline |
| Building a shared component library | CSS Modules | Prevents conflicts across projects without requiring consumers to adopt your framework |
| Rapid prototyping and iteration | Utility-First CSS | Ship UI changes without debates over semantic class names |
| Dynamic theming or personalization | CSS-in-JS | Enables runtime styling decisions, but measure the performance cost and check whether CSS custom properties already cover your theming |
Next steps: It’s worth documenting the decision before different approaches appear independently across the codebase, and updating linting rules so the team follows the strategy. You’ll also want to revisit the choice as your team and requirements change.