React: React Styling Solutions

Last updated: 2026-08-26

While maintaining a large e-commerce back-end project, Tom noticed that as the team grew, classe name conflicts in the global CSS were becoming increasingly frequent. Changing the style of a single button could affect the display on three different pages. He needed a solution that could “isolate” styles so they wouldn’t interfere with one another.


1. What You'll Learn



2. Conceptual Diagrams

The figure below illustrates the key differences among the three design approaches in terms of "writing style" and "runtime behavior":

100%
flowchart LR
    A[React Design Concepts] --> B[CSS Modules]
    A --> C[Tailwind CSS]
    A --> D[styled-components]

    B --> B1["*.module.css Documents"]
    B --> B2["Compile to Generate a Unique Hash Class Name"]
    B --> B3["Natural Barrier / Zero Runtime"]

    C --> C1["Tool Sets className"]
    C --> C2["PurgeCSS Remove unused styles"]
    C --> C3["Build at Compile Time / Zero Runtime"]

    D --> D1["JS Styling Template Strings"]
    D --> D2["Runtime Injection style Tags"]
    D --> D3["Props Driving Dynamic Styles"]

    style B fill:#e3f2fd,stroke:#1565c0
    style C fill:#e8f5e9,stroke:#2e7d32
    style D fill:#fff3e0,stroke:#e65100


3. A Real-Life Scenario

Styling Approach Isolation Runtime Overhead Dynamic Styling SSR Compatibility
Inline Styles No Isolation None ✅ Write JS Directly
CSS Modules Hash Isolation None ❌ Requires concatenating className
Tailwind CSS No minification (PurgeCSS) None ⚠️ Conditional class name combinations
styled-components Unique class name isolation Yes (runtime injection) ✅ Props-driven ⚠️ Requires SSR configuration

Tom's team maintains a backend system with over 50 pages. In the early days, they used a global CSS file, and class naming conventions were based entirely on BEM. As five front-end developers began working on the project simultaneously, the following issues arose:

Tom decided to adopt an engineering-driven styling approach to address these issues. He first tried CSS Modules, then tested Tailwind CSS, and ultimately built the design system using styled-components. Here is a complete walkthrough of all three approaches.


(1) CSS Modules — Zero-Cost Style Isolation

CSS Modules are the easiest styling isolation solution to get started with in React projects. They compile each CSS file into a module with a unique hashed class name, fundamentally eliminating class name conflicts.

Core Principle: When you write .button, it compiles to .Button_button_abc123, so other components cannot accidentally hit it.

Writing CSS Module Files

CSS
/* Button.module.css */
.button {
  padding: 10px 20px;
  border: none;
  border-radius: 6px;
  cursor: pointer;
  font-size: 14px;
  font-weight: 500;
  transition: all 0.2s ease;
}

.primary {
  background: #1890ff;
  color: white;
}

.primary:hover {
  background: #40a9ff;
}

.danger {
  background: #ff4d4f;
  color: white;
}

.danger:hover {
  background: #ff7875;
}

.default {
  background: #f0f0f0;
  color: #333;
}

.default:hover {
  background: #d9d9d9;
}

Importing and Using in Components

JSX
import styles from './Button.module.css'

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

export default Button
▶ Try it Yourself

Compiled DOM output: <button class="Button_button_1a2b3c Button_primary_4d5e6f">Submit</button>; the class name is globally unique.

Best Practices for Dynamic Class Name Concatenation

When a component needs to generate class names based on a combination of multiple conditions, we recommend using the clsx or classnames libraries to avoid excessively deep template string nesting:

BASH
npm install clsx
JSX
import styles from './Button.module.css'
import clsx from 'clsx'

function Button({ variant = 'primary', size = 'medium', disabled, children }) {
  return (
    <button
      className={clsx(
        styles.button,
        styles[variant],
        styles[size],
        { [styles.disabled]: disabled }
      )}
      disabled={disabled}
    >
      {children}
    </button>
  )
}
▶ Try it Yourself
CSS
/* Button.module.css — Add New Size Variants */
.small { padding: 4px 12px; font-size: 12px; }
.medium { padding: 8px 20px; font-size: 14px; }
.large { padding: 12px 28px; font-size: 16px; }
.disabled { opacity: 0.5; cursor: not-allowed; }

▶ Example 1: Combining Multiple Class Names with CSS Modules

Output:

TEXT 📖 Display only
Styled components with CSS-in-JS or scoped styles
JSX
import styles from './Card.module.css'

function Card({ title, children, isHighlighted }) {
  return (
    <div
      className={
        `${styles.card} ${isHighlighted ? styles.highlighted : ''}`
      }
    >
      <h3 className={styles.title}>{title}</h3>
      <div className={styles.content}>{children}</div>
    </div>
  )
}
▶ Try it Yourself

Output:

TEXT 📖 Display only
CSS Modules: import styles from "./Button.module.css". Class names are scoped: <button className={styles.primary + " " + styles.large}>
CSS
/* Card.module.css */
.card {
  border: 1px solid #e8e8e8;
  border-radius: 8px;
  padding: 16px;
  background: #fff;
}

.highlighted {
  border-color: #1890ff;
  box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
}

.title {
  font-size: 16px;
  font-weight: 600;
  margin-bottom: 8px;
  color: #1a1a1a;
}

.content {
  font-size: 14px;
  color: #666;
  line-height: 1.6;
}

Suitable Scenarios: Small- to medium-sized projects; teams with many new members; projects seeking style isolation at the lowest possible cost.


(2) Tailwind CSS — Atomic, Rapid Development

Tailwind CSS provides a set of atomic (utility-first) CSS class names. Developers don’t need to write custom CSS; instead, they build the UI by combining atomic classes in JSX.

Key Idea: Instead of naming classes, use semantically clear utility classes such as p-4 (padding: 16px), text-lg (font-size: 18px), and bg-blue-500.

Installation and Configuration

BASH
npm install -D tailwindcss @tailwindcss/vite
JS
// vite.config.js
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'

export default defineConfig({
  plugins: [react(), tailwindcss()],
})
CSS
/* index.css */
@import "tailwindcss";

▶ Example 2: Building a Card Component with Tailwind CSS

Output:

TEXT 📖 Display only
Product card: image, name, description, price ($xx.xx), "Add to Cart" button. Tailwind utility classes for responsive layout
JSX
function ProductCard({ product, onAddToCart }) {
  return (
    <div className="border border-gray-200 rounded-lg p-4 shadow-sm hover:shadow-md transition-shadow bg-white">
      <img
        src={product.image}
        alt={product.name}
        className="w-full h-48 object-cover rounded-md mb-3"
      />
      <h3 className="text-lg font-semibold text-gray-800 mb-1">
        {product.name}
      </h3>
      <p className="text-sm text-gray-500 mb-2 line-clamp-2">
        {product.description}
      </p>
      <div className="flex items-center justify-between">
        <span className="text-xl font-bold text-blue-600">
          ${product.price}
        </span>
        <button
          onClick={() => onAddToCart(product)}
          className="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-lg text-sm font-medium transition-colors"
        >
          Add to Cart
        </button>
      </div>
    </div>
  )
}
▶ Try it Yourself

Output:

TEXT 📖 Display only
Renders: 
        {product.name}
       + 
        {product.description}
      

Advantages of Tailwind:

Use Cases: Rapid prototyping, startup projects, and medium- to large-scale projects where teams adopt a unified atomic design approach.


(3) styled-components — CSS-in-JS Design System

styled-components is a leading implementation of CSS-in-JS, where styles are written using template strings in JavaScript, and the styles are tightly coupled with the components.

Installation

BASH
npm install styled-components

▶ Example 3: The styled-components Themed Button System

Output:

TEXT 📖 Display only
Displays: "Add to Cart". Button: onAddToCart(product)}          className="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-lg text-sm font-medium transition-colors"        >          Add to Cart
JSX
import styled, { ThemeProvider } from 'styled-components'

// Define the Topic
const theme = {
  colors: {
    primary: '#1890ff',
    success: '#52c41a',
    danger: '#ff4d4f',
    text: '#333',
    textLight: '#fff',
  },
  radii: {
    sm: '4px',
    md: '8px',
    lg: '12px',
  },
  fonts: {
    body: "'Inter', sans-serif",
  },
}

// Styled Components — Automatic Receipt theme prop
const StyledButton = styled.button`
  padding: 10px 24px;
  border: none;
  border-radius: ${props => props.theme.radii.md};
  font-family: ${props => props.theme.fonts.body};
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  transition: all 0.2s ease;

  /* props Conditional Styles Driven by Rules */
  background: ${props => {
    if (props.$variant === 'danger') return props.theme.colors.danger
    if (props.$variant === 'success') return props.theme.colors.success
    return props.theme.colors.primary
  }};
  color: ${props => props.theme.colors.textLight};

  &:hover {
    opacity: 0.85;
    transform: translateY(-1px);
  }

  &:disabled {
    opacity: 0.5;
    cursor: not-allowed;
    transform: none;
  }
`

const ButtonGroup = styled.div`
  display: flex;
  gap: 12px;
  padding: 20px;
`

function App() {
  return (
    <ThemeProvider theme={theme}>
      <ButtonGroup>
        <StyledButton $variant="primary">Main Buttons</StyledButton>
        <StyledButton $variant="success">Success Button</StyledButton>
        <StyledButton $variant="danger" disabled>Danger(Disable)</StyledButton>
      </ButtonGroup>
    </ThemeProvider>
  )
}

Output:

TEXT 📖 Display only
❌ Hooks inside conditions cause bugs. ✅ Always call hooks at top level. Rule: Hooks must be called in same order every render.

Key Features of styled-components:

Use Cases: Large-scale design systems, scenarios requiring dynamic theme switching, and UI component library development.



4. Comparison of Solution Options

Dimension CSS Modules Tailwind CSS styled-components
Learning Curve Low (Standard CSS) Medium (Requires memorizing class names) Medium (Requires understanding CSS-in-JS)
Style Isolation Full Isolation (Hash) None (Global Class Names) Full Isolation (Unique Class Names)
Runtime overhead None None Yes (injects a style tag)
Dynamic Styles Concatenating via JS className Conditional Class Name Combination Directly Driven by Props
Theme System Requires an additional solution CSS variables + configuration Built into ThemeProvider
Build Output Separate CSS File Minimal Size After PurgeCSS Included in JS Bundle
Optimal Project Size Small to Medium Medium to Large Large/Design System


5. Recommendations for Model Selection

Team/Project Details Recommended Solution
Teams of 2–5 people, small to medium-sized projects CSS Modules (the most cost-effective way to achieve isolation)
Startup teams need rapid iteration Tailwind CSS (no need to write CSS, faster development)
Large-scale enterprise projects, system design styled-components (comprehensive theme system)
Migration of Existing Projects Keep the current approach and gradually replace new modules with CSS Modules
Component Library Development styled-components or CSS Modules (either is acceptable)

▶ Example 4: CSS Modules Component Styles

Output:

TEXT 📖 Display only
Displays: "Main Buttons"
JSX
// Button.module.css
// .primary { background: #1677ff; color: white; border: none; padding: 8px 16px; border-radius: 6px; cursor: pointer; }
// .danger { background: #ff4d4f; color: white; }
// .outline { background: transparent; border: 1px solid #1677ff; color: #1677ff; }
// .small { padding: 4px 8px; font-size: 12px; }
// .disabled { opacity: 0.5; cursor: not-allowed; }

import styles from './Button.module.css'

function Button({ variant = 'primary', size, disabled, children, ...props }) {
  const classNames = [
    styles[variant],
    size === 'sm' && styles.small,
    disabled && styles.disabled,
  ].filter(Boolean).join(' ')

  return (
    <button className={classNames} disabled={disabled} {...props}>
      {children}
    </button>
  )
}

function ButtonDemo() {
  return (
    <div style={{ display: 'flex', gap: 8 }}>
      <Button>Primary</Button>
      <Button variant="danger">Delete</Button>
      <Button variant="outline" size="sm">Small Outline</Button>
      <Button disabled>Disabled</Button>
    </div>
  )
}
▶ Try it Yourself

Output:

TEXT 📖 Display only
CSS Modules: .card { ... } → unique class name .card_abc123. No global conflicts. Import as styles object: className={styles.card}

▶ Example 5: Responsive Card Grid with Tailwind CSS

Output:

TEXT 📖 Display only
Displays: "Primary". Button: {children}
TSX
function ProductGrid({ products }) {
  return (
    <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6 p-4">
      {products.map(product => (
        <div key={product.id} className="bg-white rounded-lg shadow-md hover:shadow-xl transition-shadow duration-300 overflow-hidden">
          <div className="h-48 bg-gray-200 flex items-center justify-center">
            <span className="text-gray-400 text-4xl">📦</span>
          </div>
          <div className="p-4">
            <h3 className="font-semibold text-gray-800 truncate">{product.name}</h3>
            <p className="text-sm text-gray-500 mt-1">{product.category}</p>
            <div className="flex items-center justify-between mt-3">
              <span className="text-lg font-bold text-red-500">${product.price}</span>
              <button className="bg-blue-500 hover:bg-blue-600 text-white text-sm px-3 py-1 rounded transition-colors">
                Add to Cart
              </button>
            </div>
          </div>
        </div>
      ))}
    </div>
  )
}

Output:

TEXT 📖 Display only
Tailwind responsive grid: 1 col (mobile) → 2 cols (md) → 3 cols (lg). Cards with image, title, description, and CTA button

❓ FAQ

Q Which has better performance, CSS Modules or CSS-in-JS?
A CSS Modules offer better performance because they generate static CSS files during the build process, eliminating runtime overhead. Styled-Components injects style tags into the DOM during rendering, resulting in a slight additional overhead during the first render. For most applications, the difference is negligible, but CSS Modules have an advantage in performance-sensitive animation scenarios.
Q What can I do if Tailwind CSS class names are too long and affect code readability?
A You can use the @apply directive to extract multiple utility classes into custom CSS classes, or use the clsx library for conditional concatenation. Additionally, most editors (such as VS Code with Tailwind CSS IntelliSense) offer class name autocompletion and hover previews, so class name readability isn’t an issue in actual development.
Q Why does $variant in styled-components have the $ prefix?
A The $ prefix is part of the "transient prop" convention introduced in styled-components v5.2+, indicating that this prop is used only for style calculations and will not be passed down to the underlying DOM elements. Without the $ prefix, the prop will be rendered as an HTML attribute (such as <button variant="primary">), resulting in a console warning.
Q Can I mix and match multiple styling solutions within the same React project?
A Yes, and it’s quite common. The recommended approach is to use Tailwind CSS for global layouts (for quick framework setup), CSS Modules for business components (for stability and reliability), and styled-components for core components of the design system (for flexible theming). These three solutions do not conflict with each other because they use different compilation mechanisms.
Q Does CSS-in-JS have a significant runtime overhead? Does it affect performance?
A Runtime CSS-in-JS (such as styled-components) does indeed have some overhead—it generates classNames and inserts <style> tags every time a component renders. However, the actual impact is usually negligible because: ① React 18’s concurrent rendering has already mitigated rendering blocking; ② most performance bottlenecks stem from JavaScript logic rather than style calculations; ③ if you do encounter performance issues, you can switch to a zero-runtime solution (such as vanilla-extract or Panda CSS).

📖 Summary


📝 Exercises

  1. Create a Avatar component using CSS Modules: It supports two props, size (small/medium/large) and shape (circle/square), with the three sizes corresponding to different widths and heights.
  2. Rewrite the Avatar component above using Tailwind CSS, keeping responsive design in mind: use small on mobile devices, and it will automatically switch to medium on desktop.
  3. Use styled-components to create a Badge component that controls the displayed number via the count prop; if the number exceeds 99, display "99+"; and use the theme.colors prop to set different colors.
Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏