React does not force one styling solution. A React component is simply a JavaScript function that returns UI, so you can style that UI with regular CSS files, inline styles, CSS Modules, utility-first frameworks like Tailwind CSS, or CSS-in-JS libraries such as styled-components. The best choice depends on team preference, project size, and how much style isolation or dynamic behavior you need.
The important idea is to keep styles maintainable. As components grow, styling decisions affect readability, reuse, naming collisions, and even performance. That is why it helps to understand the trade-offs between the main approaches instead of treating them as interchangeable.
| Approach | Strength | Weakness | Best fit |
|---|---|---|---|
| Inline styles | Simple for dynamic one-off values | No pseudo-classes or media queries | Small dynamic style objects |
| Global CSS files | Familiar and easy to start with | Class name conflicts can grow over time | Small to medium projects |
| CSS Modules | Scoped class names without conflicts | Slightly more verbose imports | Component-based apps needing isolation |
| Styled Components | Co-locates styles with components and supports dynamic props | Runtime overhead and different authoring model | Teams that prefer CSS-in-JS |
| Tailwind CSS | Fast utility-based styling with consistency | Long class strings if unmanaged | Design systems and utility-first workflows |
.card {
padding: 16px;
border-radius: 10px;
border: 1px solid #d0d7de;
background: #ffffff;
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.06);
}
.card-title {
margin-bottom: 8px;
color: #1f2937;
}
import './Card.css'
function Card({ title, children, highlight = false }) {
return (
<div
className="card"
style={{ borderColor: highlight ? '#2563eb' : '#d0d7de' }}
>
<h3 className="card-title">{title}</h3>
<div>{children}</div>
</div>
)
}
This combination is simple and practical. Use CSS classes for most styling, and use inline styles only for small dynamic values such as widths, colors, or transforms that depend directly on props or state.
CSS Modules solve one of the biggest pain points of global CSS: naming collisions. Each class name is scoped to the component file, so you can use common names like button, title, or container without worrying about another file overriding them.
.button {
border: none;
border-radius: 8px;
padding: 10px 16px;
font-weight: 600;
cursor: pointer;
}
.primary {
background: #2563eb;
color: white;
}
.danger {
background: #dc2626;
color: white;
}
.disabled {
opacity: 0.6;
cursor: not-allowed;
}
import clsx from 'clsx'
import styles from './Button.module.css'
function Button({ label, variant = 'primary', disabled = false }) {
return (
<button
className={clsx(
styles.button,
styles[variant],
{ [styles.disabled]: disabled }
)}
disabled={disabled}
>
{label}
</button>
)
}
Styled Components is a popular CSS-in-JS library. It allows you to define styles directly in JavaScript and vary them based on props. This can feel natural in component-driven design, especially when themes and prop-based variants are common.
import styled from 'styled-components'
const Button = styled.button`
border: none;
border-radius: 8px;
padding: 10px 16px;
font-weight: 600;
color: white;
background: ${props => props.variant === 'danger' ? '#dc2626' : '#2563eb'};
&:hover {
opacity: 0.92;
}
&:disabled {
opacity: 0.6;
cursor: not-allowed;
}
`
function App() {
return (
<div>
<Button>Save</Button>
<Button variant="danger">Delete</Button>
</div>
)
}
Tailwind CSS uses small utility classes instead of writing most component CSS manually. This can speed up UI development and encourage consistent spacing, typography, and color usage across a React project.
import clsx from 'clsx'
import { twMerge } from 'tailwind-merge'
function cn(...inputs) {
return twMerge(clsx(inputs))
}
function Button({ label, variant = 'primary', disabled = false }) {
const classes = cn(
'rounded-md px-4 py-2 font-semibold transition-colors',
variant === 'primary' && 'bg-blue-600 tl-text-white hover:bg-blue-700',
variant === 'danger' && 'bg-red-600 tl-text-white hover:bg-red-700',
disabled && 'cursor-not-allowed opacity-60'
)
return (
<button className={classes} disabled={disabled}>
{label}
</button>
)
}
| Mistake | Why it causes trouble | Better approach |
|---|---|---|
| Putting all styles in one global file | Creates collisions and hard-to-track overrides | Split styles by component or feature |
| Using inline styles for everything | Loses pseudo-classes, media queries, and maintainability | Reserve inline styles for small dynamic cases |
| Mixing many styling systems without a rule | Makes the codebase inconsistent | Choose a primary approach and use it consistently |
| Hardcoding design values everywhere | Makes design updates difficult | Use tokens, variables, or reusable classes |
clsx when styles depend on propsReact supports many valid styling approaches, and each one has its own trade-offs. Global CSS is simple, CSS Modules provide scoped styles, Styled Components combine styles with component logic, and Tailwind CSS offers a utility-first workflow. The right choice depends on the size of the project, the team's preferences, and the design system needs.
The most important thing is not choosing the trendiest option. It is choosing an approach that keeps your components readable, your styles maintainable, and your UI consistent as the application grows.
Explore 500+ free tutorials across 20+ languages and frameworks.