Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
React does not mandate a CSS system. You can use ordinary stylesheets, CSS Modules, inline style objects, CSS-in-JS, or utility classes such as Tailwind. The right choice depends on whether your values are static or data-driven, how strongly you need style scoping, and whether you prefer selectors in CSS files or decisions beside JSX.
This guide shows the five approaches with runnable examples, state and responsive behavior, trade-offs, and a practical decision framework for 2026 projects.
1. Plain or global CSS with className
React’s baseline pattern is a normal CSS class in JSX and rules in a separate stylesheet. React calls the prop className, and it does not prescribe how you add CSS files.
Example
/* Button.css */
.button {
background: #2563eb;
border: 0;
border-radius: 0.5rem;
color: white;
cursor: pointer;
padding: 0.65rem 1rem;
}
.button:hover {
background: #1d4ed8;
}
@media (max-width: 640px) {
.button { width: 100%; }
}
import './Button.css';
export default function Button({ children }) {
return <button className="button">{children}</button>;
}
This is the clearest option when your team already knows CSS and wants browser-native selectors, pseudo-classes, media queries, and familiar debugging in DevTools. It is largely build-time work: the browser receives a stylesheet and applies the cascade.
#1 Best Overall
Where it fits
- Site-wide typography, resets, tokens, and layout primitives.
- Teams that want styles independent of JavaScript and framework conventions.
- Projects where global naming discipline is acceptable.
The cost is collision risk. A generic name such as .card can affect unrelated components. Use a naming convention (for example, profile-card__title), layers, or a small set of global component primitives.
2. CSS Modules
CSS Modules keep ordinary CSS syntax while your build tool processes each module separately. A class imported from a module is transformed into a scoped name, reducing accidental collisions while preserving selectors, pseudo-classes, and media queries. Vite, Parcel, and Turbopack support processing CSS Modules separately; exact scoping behavior depends on the project and build configuration.
Example
/* Card.module.css */
.card {
background: white;
border: 1px solid #e5e7eb;
border-radius: 0.75rem;
padding: 1rem;
}
.title {
color: #111827;
font-size: 1.125rem;
}
.card:hover { box-shadow: 0 8px 24px rgb(0 0 0 / 12%); }
import styles from './Card.module.css';
export default function Card({ title, children }) {
return (
<article className={styles.card}>
<h2 className={styles.title}>{title}</h2>
{children}
</article>
);
}
Modules are a strong default for component-level static styling when you want CSS files and predictable selectors without global naming anxiety. They can coexist with Tailwind, but confirm how your framework handles composition, ordering, and global selectors.
Common caveats
- Global selectors such as
:global(...)are build-tool-specific. - Class names are transformed, so selectors copied into external HTML or tests may not match.
- Sharing styles across modules requires deliberate composition or shared global tokens.
3. Inline style objects
React’s style prop accepts a JavaScript object. It is most useful when values depend on props, state, calculations, or API data.
Example
export default function Meter({ value }) {
const width = `${Math.max(0, Math.min(value, 100))}%`;
return (
<div style={{ backgroundColor: '#e5e7eb', borderRadius: 8 }}>
<div
role="progressbar"
aria-valuenow={value}
style={{ backgroundColor: value > 70 ? '#16a34a' : '#f59e0b', height: 10, width }}
/>
</div>
);
}
Numeric values are generally interpreted as pixels where CSS permits lengths; unit-bearing values remain strings. CSS custom properties can also be supplied as object keys such as '--accent'.
What inline styles cannot replace
An inline object does not provide stylesheet selectors. It cannot by itself express :hover, :focus, media queries, or pseudo-elements. Tailwind’s documentation makes the same distinction: inline styles cannot target states such as hover or focus, while utility variants can.
Keep static rules in CSS and use inline values only for the dynamic part. This avoids recreating large objects on every render, keeps accessibility states visible, and makes responsive behavior possible through a stylesheet.
4. CSS-in-JS with styled-components
styled-components uses tagged template literals to create React components with attached styles. Because it generates a stylesheet rather than relying only on inline declarations, normal CSS features—including pseudo-selectors and media queries—remain available.
Rank #3
Example
import styled from 'styled-components';
const Button = styled.button`
background: ${({ $danger }) => ($danger ? '#dc2626' : '#2563eb')};
border: 0;
border-radius: 0.5rem;
color: white;
padding: 0.65rem 1rem;
&:hover { filter: brightness(0.92); }
@media (max-width: 640px) {
width: 100%;
}
`;
export default function Actions() {
return <Button $danger>Delete</Button>;
}
The transient $danger prop controls a variant without being forwarded as an invalid DOM attribute. This style suits teams that want component APIs, prop-driven variants, and colocated themes.
Architecture checks before standardizing
- Confirm the library version supports your React rendering and server-rendering setup.
- Account for generated style ordering when mixing with global CSS or third-party components.
- Measure the runtime and server-rendering implications for your application rather than assuming they are free.
- Define a theme contract so colors, spacing, and typography do not become arbitrary prop values.
5. Utility classes with Tailwind CSS
Tailwind composes single-purpose classes directly in markup. Responsive and state variants such as md:, hover:, and focus: keep common behavior close to the element.
Example
export default function Card({ title, children }) {
return (
<article className="rounded-xl border border-slate-200 bg-white p-4 shadow-sm transition hover:shadow-lg md:p-6">
<h2 className="text-lg font-semibold text-slate-900">{title}</h2>
<div className="mt-2 text-slate-600">{children}</div>
</article>
);
}
Use arbitrary values when a one-off value is genuinely required, and use inline styles for values arriving from a database or API. Tailwind also allows plain CSS for custom rules and base or component layers.
Keeping utility markup maintainable
- Extract repeated combinations into React components instead of copying long class strings.
- Use a consistent ordering convention for utilities.
- Keep dynamic class names explicit enough for your build scanner to detect; do not construct unknown class fragments at runtime.
- Reserve custom CSS for patterns that utilities cannot express cleanly.
Which React styling method should you choose?
| Method | Best for | Scope | Dynamic values | States and media queries | Main trade-off |
|---|---|---|---|---|---|
| Plain CSS | Standard CSS files and global foundations | Global unless names are disciplined | Via classes or CSS variables | Yes | Possible name collisions |
| CSS Modules | Component CSS with familiar syntax | Build-time module boundary | Via classes or CSS variables | Yes | Behavior depends on build tooling |
| Inline objects | Values calculated in JavaScript | Element-level | Directly | Not by themselves | No selectors, hover, focus, or media queries |
| styled-components | Colocated APIs, variants, and themes | Generated component styles | Directly through props | Yes | Runtime and SSR architecture need review |
| Tailwind | Constrained vocabulary and rapid composition | Utility classes | Classes or inline values | Yes, through variants | Class strings can become dense |
A practical decision process
- Start with value type. If most values are fixed design tokens, choose plain CSS or CSS Modules. If values come from state or data, add inline values or prop-driven styled components where appropriate.
- Decide your collision boundary. Choose global CSS only with naming and layering rules; choose CSS Modules when each component should have an automatic boundary.
- List required selectors. Hover, focus, disabled, pseudo-elements, and responsive breakpoints require stylesheet-capable methods or Tailwind variants.
- Choose authoring location. Keep CSS in files for separation and browser-native inspection; colocate with styled-components or utility classes when component APIs are the priority.
- Validate the rendering architecture. For CSS-in-JS, test server rendering, streaming, hydration, style order, and extraction with your actual framework.
- Standardize exceptions. Document when inline styles, arbitrary Tailwind values, global selectors, or custom CSS are allowed.
Debugging and failure modes
Class has no visible effect
Inspect the element and verify the class name is actually present. In CSS Modules, use styles.name, not the literal source name. Check specificity, import order, and whether a reset or utility class wins the cascade.
Rank #4
Hover or focus styling does not work
Inline objects cannot express these states alone. Move the rule to a stylesheet, styled-component, or Tailwind variant. Also check keyboard focus visibility rather than removing outlines without a replacement.
Styles work in development but disappear in production
Check the production build’s CSS-module and Tailwind configuration. Dynamically constructed Tailwind class names may not be detected; use complete class strings or a safelist supported by your setup.
Server-rendered page flashes unstyled content
For CSS-in-JS, follow the library’s server extraction and hydration setup and verify that server and client render the same component tree. For stylesheet methods, ensure the generated CSS is linked before interactive content is displayed.
Free tools Windows power users keep installed
One-click scans. No signup required.
Responsive rule is ignored
Confirm the viewport meta tag, media-query syntax, and source order. In utility systems, verify the breakpoint prefix and that a later utility is not overriding it.
Best Value
Preview a styled React page without setting up a browser
When you need a rendered PNG, JPEG, WebP, or PDF of a deployed React route, ScreenshotNeo provides a GET-based screenshot API. It accepts cookie and consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be disabled. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing result.
Or skip the browser setup:
Use the API documented at https://screenshotneo.com/docs/:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://your-react-site.example -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://your-react-site.example"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://your-react-site.example' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
ScreenshotNeo also supports full-page captures with lazy images loaded, CSS-selector element captures, dark mode, device presets and custom viewports, retina scale, PDF options, custom CSS and JavaScript, click and wait actions, request blocking, headers, cookies, user agents, authorization, timezone and geolocation, transparent backgrounds, resizing, selectable cache TTLs, signed image links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, a usage API, and an OpenAPI specification. Its parameter names are compatible with those used by other screenshot APIs, easing migration.
The Tool Desk
Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →The free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 shots; every feature is included on every plan. An MCP server provides take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients. Sign up free for ScreenshotNeo.
Frequently Asked Questions
Can I mix these methods in one React application?
Yes. A common boundary is global CSS for resets and tokens, CSS Modules or styled-components for components, and inline values only where data determines a property. Document ownership and cascade rules so the combination remains predictable.
Is one method officially recommended by React?
No. React supports both className and style objects and does not prescribe a CSS framework. Choose based on your selectors, scoping, runtime, and team workflow.
Do CSS Modules eliminate all global CSS?
No. Resets, font-face declarations, design tokens, and third-party overrides may still need global styles, depending on your build setup.
Do these 3 things before closing this tab:
1Scan for outdated or missing drivers - takes under a minute2Repair Windows errors before they cause bigger problems3Fix the driver behind crashes, sound loss and screen glitchesQuick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

