Styling Approaches Overview
React does not ship with a built-in styling system. That is intentional — it lets teams choose the approach that best fits their project. The result is a rich ecosystem of options, each with distinct trade-offs around performance, developer experience, SSR compatibility, and bundle size. Understanding those trade-offs is what separates junior React developers from senior ones.
There are five mainstream approaches used in production React applications today: Global CSS, CSS Modules, CSS-in-JS (Styled Components, Emotion), Utility-first CSS (Tailwind), and Inline styles. This page maps out all five so you can make informed decisions.
1. Global CSS
The simplest approach: write .css files and import them. Styles are globally scoped — any rule you write applies to every element on the page that matches the selector.
/* styles/global.css */
.button {
background: #0070f3;
color: white;
padding: 8px 16px;
border-radius: 4px;
}
.button:hover {
background: #0051a2;
}import './styles/global.css'
function Button({ children }) {
return <button className="button">{children}</button>
}Global CSS works fine for small apps or when you deliberately want site-wide rules (resets, typography scales, CSS custom properties). It breaks down in larger codebases: class names collide, specificity wars break out, and deleting a CSS rule becomes dangerous because you never know what depends on it.
2. CSS Modules
CSS Modules give each .module.css file a local scope by transforming class names at build time. The class .button in Button.module.css becomes something like Button_button__xK2qP in the final output, making collisions impossible.
/* Button.module.css */
.button {
background: #0070f3;
color: white;
padding: 8px 16px;
}import styles from './Button.module.css'
function Button({ children }) {
return <button className={styles.button}>{children}</button>
}CSS Modules are supported out of the box in Vite, Create React App, and Next.js. They require zero runtime, work perfectly with SSR, and give you full access to the CSS language. The limitation is that dynamic styles (e.g., a color that changes based on a prop) are awkward — you end up using CSS custom properties or toggling class names.
3. CSS-in-JS (Styled Components & Emotion)
CSS-in-JS libraries let you write CSS directly inside JavaScript using tagged template literals. The component and its styles live in the same file, and dynamic styling based on props is first-class.
import styled from 'styled-components'
const Button = styled.button`
background: ${(props) => (props.primary ? '#0070f3' : 'white')};
color: ${(props) => (props.primary ? 'white' : '#0070f3')};
padding: 8px 16px;
border-radius: 4px;
`
// Usage:
<Button primary>Save</Button>
<Button>Cancel</Button>The developer experience is excellent — colocation of styles and logic, automatic vendor prefixing, theming via ThemeProvider. The cost is a JavaScript runtime: styles are injected via <style> tags at runtime, which adds CPU overhead and can cause flashes during SSR hydration. This is why the industry has been moving away from runtime CSS-in-JS since ~2022.
4. Utility-First CSS (Tailwind)
Tailwind CSS provides thousands of atomic utility classes — flex, pt-4, text-blue-500, rounded-lg — that you compose directly in JSX. There is no separate CSS file to maintain; the styles live in the className prop.
function Button({ children, primary }) {
return (
<button
className={
primary
? 'bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700'
: 'border border-blue-600 text-blue-600 px-4 py-2 rounded hover:bg-blue-50'
}
>
{children}
</button>
)
}Tailwind's build step strips unused classes, so production bundles are tiny (typically 5–15 KB). It dominates the React ecosystem in 2024–2025, powering major component libraries like Shadcn/ui, Radix UI themes, and Headless UI. The trade-off is a steep initial learning curve and verbose className strings for complex components.
5. Inline Styles
React's style prop accepts a JavaScript object with camelCase property names. No build step, no class names, purely dynamic — but no access to pseudo-selectors, media queries, or animations.
function ProgressBar({ value }) {
return (
<div style={{ background: '#e5e7eb', height: 8, borderRadius: 4 }}>
<div
style={{
width: `${value}%`,
background: '#0070f3',
height: '100%',
borderRadius: 4,
transition: 'width 0.3s ease',
}}
/>
</div>
)
}Inline styles are best reserved for truly dynamic computed values — things like animation percentages, chart dimensions, or user-chosen colors — where you need JavaScript-computed values that cannot be expressed as static CSS class names.
Comparison Table
Approach | Scoping | Dynamic styling | SSR support | Runtime cost | DX | Bundle size impact |
|---|---|---|---|---|---|---|
Global CSS | None (global) | Manual class toggling | Excellent | None | Simple but fragile | Small |
CSS Modules | Auto per-file | CSS custom props / class toggle | Excellent | None | Great | Small |
Styled Components | Automatic | First-class (props) | Complex (SSR setup) | Medium–High | Excellent | Medium (+runtime) |
Emotion | Automatic | First-class (props) | Complex | Medium | Excellent | Medium (+runtime) |
Tailwind | Atomic / global | Class composition | Excellent | None (build-time) | Great (after ramp-up) | Tiny (purged) |
Inline styles | Element-level | Native (JS object) | Excellent | None | Limited (no pseudo) | None |
When to Pick Each
Global CSS — site-wide resets, design tokens as CSS custom properties, or tiny prototypes
CSS Modules — component libraries, open-source packages, teams that want zero runtime and full CSS power
Styled Components / Emotion — existing codebases built on them; avoid for new projects
Tailwind — the default choice for most new React/Next.js apps in 2024–2025; excellent for design-system-free projects
Inline styles — dynamic computed values (chart widths, user-picked colors, animated numeric properties)
The Evolution of React Styling
The history of React styling is a story of progressively eliminating global scope. In 2015 everyone used global CSS with BEM naming conventions. In 2016–2018 CSS Modules gained traction, followed by an explosion of CSS-in-JS solutions (Styled Components 2016, Emotion 2017). By 2020 Tailwind started taking over new projects. By 2023 the React team itself began discouraging runtime CSS-in-JS in the Server Components era, as injecting <style> tags at runtime is fundamentally incompatible with server-first rendering.
Current industry direction (2025): Tailwind for application UIs, CSS Modules for library authors who need zero runtime and maximum compatibility, and CSS-in-JS only in legacy codebases. Both styled-components and emotion are in maintenance mode for their runtime variants and are actively developing compile-time / static extraction modes to remove the runtime cost.