1. đ Introduction
Styling is one of the most opinionated areas of React development â there's no single "official" way to do it. Instead, React offers a rich ecosystem of approaches, from plain CSS stylesheets to fully-fledged CSS-in-JS libraries and utility-first frameworks like Tailwind CSS. This tutorial covers the full spectrum: core techniques, popular libraries, theming, responsiveness, animations, and the trade-offs that help you pick the right tool for each project.
Information
2. đ¨ Styling React Components
Because React components are just JS functions that return markup, styling can be attached in several fundamentally different ways: via external stylesheets, scoped modules, inline objects, or by generating CSS directly from your component code.
3. đ CSS Stylesheets
The simplest approach: write a regular .css file and import it directly into your component. Styles apply globally by default, based on class name.
Button.jsx
import "./Button.css";
function Button({ children }) {
return <button className="btn-primary">{children}</button>;
}Button.css
.btn-primary {
background-color: #2563eb;
color: white;
padding: 8px 16px;
border-radius: 6px;
}Caution
4. đ§Š CSS Modules
CSS Modules solve the naming-collision problem by automatically scoping class names to the component that imports them. Files are named with a .module.css suffix.
Card.jsx
import styles from "./Card.module.css";
function Card({ title }) {
return <div className={styles.card}><h3 className={styles.title}>{title}</h3></div>;
}Card.module.css
.card {
border: 1px solid #e5e7eb;
border-radius: 8px;
padding: 16px;
}
.title {
font-weight: 600;
}Best Practice
5. đī¸ Inline Styles
React also accepts a style prop containing a plain JS object, with camelCased property names instead of hyphenated CSS ones.
InlineStyle.jsx
function Alert() {
return (
<div style={{ backgroundColor: "#fee2e2", color: "#991b1b", padding: "12px" }}>
Something went wrong!
</div>
);
}Warning
6. đ Dynamic Styles
Because inline style objects and class names are just JS values, they can be computed from props or state at render time.
ProgressBar.jsx
function ProgressBar({ percent }) {
return (
<div style={{ width: "100%", background: "#e5e7eb" }}>
<div style={{ width: `${percent}%`, background: "#22c55e", height: "8px" }} />
</div>
);
}7. đ Conditional Styling
Class names can be toggled based on state or props using template literals, ternaries, or a helper utility like clsx.
ConditionalStyle.jsx
import clsx from "clsx";
function Tab({ isActive, children }) {
return (
<button className={clsx("tab", isActive && "tab-active")}>
{children}
</button>
);
}Tip
8. đī¸ CSS Variables
CSS custom properties (variables) let you define reusable values â colors, spacing, fonts â that can be read and even overridden dynamically from React via inline styles.
variables.css
:root {
--color-primary: #2563eb;
--spacing-md: 16px;
}
.btn {
background: var(--color-primary);
padding: var(--spacing-md);
}ThemedBox.jsx
function ThemedBox({ accentColor }) {
return <div style={{ "--accent": accentColor }} className="themed-box" />;
}9. đ Sass (SCSS)
Sass extends CSS with variables, nesting, mixins, and functions, then compiles down to plain CSS. It pairs naturally with CSS Modules as .module.scss.
Button.module.scss
$primary: #2563eb;
.btn {
background: $primary;
&:hover {
background: darken($primary, 10%);
}
}10. đ¯ Less
Less is a similar CSS preprocessor to Sass, historically popular with libraries like Ant Design, which exposes Less variables for theme customization.
theme.less
@primary-color: #1890ff;
.btn {
background: @primary-color;
border-radius: 4px;
}11. đ Styled Components
styled-components is a popular CSS-in-JS library that lets you write actual CSS inside tagged template literals, generating uniquely-scoped React components.
StyledButton.jsx
import styled from "styled-components";
const Button = styled.button`
background: ${(props) => (props.primary ? "#2563eb" : "#e5e7eb")};
color: ${(props) => (props.primary ? "white" : "black")};
padding: 8px 16px;
border-radius: 6px;
&:hover {
opacity: 0.9;
}
`;
function App() {
return <Button primary>Click me</Button>;
}Read more in the Styled Components documentation.
12. đ Emotion
Emotion is another popular CSS-in-JS library, offering both a styled API similar to Styled Components and a lightweight css prop for one-off styling.
EmotionExample.jsx
/** @jsxImportSource @emotion/react */
import { css } from "@emotion/react";
function Badge() {
return (
<span
css={css`
background: #fef3c7;
padding: 4px 8px;
border-radius: 999px;
`}
>
New
</span>
);
}13. đŦī¸ Tailwind CSS
Tailwind CSS is a utility-first framework: instead of writing custom CSS classes, you compose pre-defined utility classes directly in your JSX.
TailwindButton.jsx
function Button({ children }) {
return (
<button className="bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700">
{children}
</button>
);
}Tip
14. đ§ą Utility-First CSS
The utility-first philosophy behind Tailwind favors small, single-purpose classes (flex, p-4, text-center) over custom, semantically-named classes. This trades some markup verbosity for faster iteration and fewer naming decisions.
| Aspect | Utility-First | Semantic CSS |
|---|---|---|
| Naming overhead | Minimal | High (BEM, etc.) |
| Markup verbosity | Higher | Lower |
| Reusability | Via components | Via class names |
15. đ§Ŧ CSS-in-JS
CSS-in-JS is an umbrella term for libraries â like Styled Components and Emotion â that let you author CSS directly within JavaScript, gaining access to props, theme context, and dynamic logic.
- Pros: automatic scoping, dynamic styling via props, colocated styles and logic
- Cons: runtime overhead (for some libraries), extra bundle size, potential FOUC without proper SSR setup
16. đ ąī¸ Styling with Bootstrap
Bootstrap can be used in React either via its plain CSS classes or through react-bootstrap, which wraps components as proper React elements.
BootstrapButton.jsx
import Button from "react-bootstrap/Button";
function App() {
return <Button variant="primary">Click me</Button>;
}17. đ¨ Styling with Material UI
Material UI (MUI) implements Google's Material Design system as ready-made React components, with a powerful sx prop and theming system for customization.
MuiButton.jsx
import Button from "@mui/material/Button";
function App() {
return <Button variant="contained" sx={{ borderRadius: 2 }}>Click me</Button>;
}18. đ Styling with Chakra UI
Chakra UI combines accessible, composable components with a utility-style prop API and a built-in theming/dark-mode system.
ChakraButton.jsx
import { Button } from "@chakra-ui/react";
function App() {
return <Button colorScheme="blue" borderRadius="md">Click me</Button>;
}19. đ Styling with Ant Design
Ant Design is a comprehensive component library popular in enterprise and admin dashboards, with theming powered by Less variables or a newer ConfigProvider token system.
AntdButton.jsx
import { Button } from "antd";
function App() {
return <Button type="primary">Click me</Button>;
}20. đ Theme Management
Most styling solutions support a theme object â centralized colors, spacing, and typography â shared across the app via Context or a library's own ThemeProvider.
ThemeProvider.jsx
import { ThemeProvider } from "styled-components";
const theme = {
colors: { primary: "#2563eb", danger: "#dc2626" },
spacing: { sm: "8px", md: "16px" },
};
function App() {
return (
<ThemeProvider theme={theme}>
<MyApp />
</ThemeProvider>
);
}Best Practice
21. đ Dark Mode
Dark mode can be implemented via a data-theme attribute on the root element combined with CSS variables, or through a library's built-in color mode support.
darkmode.css
:root {
--bg: white;
--text: black;
}
[data-theme="dark"] {
--bg: #111827;
--text: #f9fafb;
}
body {
background: var(--bg);
color: var(--text);
}ThemeToggle.jsx
function ThemeToggle() {
const [theme, setTheme] = useState("light");
useEffect(() => {
document.documentElement.setAttribute("data-theme", theme);
}, [theme]);
return (
<button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>
Toggle theme
</button>
);
}22. đą Responsive Design
Responsive layouts adapt to different screen sizes using CSS media queries, flexible units, or a framework's built-in breakpoint system (like Tailwind's sm:, md:, lg: prefixes).
ResponsiveTailwind.jsx
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<Card />
<Card />
<Card />
</div>responsive.css
.grid {
display: grid;
grid-template-columns: 1fr;
}
@media (min-width: 768px) {
.grid {
grid-template-columns: repeat(2, 1fr);
}
}23. đ§Š Component-Based Styling
Regardless of the underlying tool, styles in React are best organized per component â colocated with the markup they affect, rather than in one giant global stylesheet.
24. đ Global Styles
Some styles genuinely belong at the application level â CSS resets, base typography, and root CSS variables. These are typically imported once, in the app's entry file.
index.jsx
import "./index.css"; // global reset + base styles
import App from "./App";25. đ Scoped Styles
Scoped styling â via CSS Modules, Styled Components, or Emotion â ensures a component's styles cannot accidentally leak out and affect unrelated parts of the page.
Reference
26. đŦ Animations
CSS @keyframes can be defined in a stylesheet and triggered by toggling a class name in React.
animations.css
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.fade-in {
animation: fadeIn 0.3s ease-in;
}FadeInBox.jsx
function Toast({ message }) {
return <div className="fade-in">{message}</div>;
}Tip
27. đ Transitions
CSS transition smoothly animates property changes â like opacity or transform â whenever a class or inline style updates.
Collapsible.jsx
function Collapsible({ isOpen, children }) {
return (
<div
style={{
maxHeight: isOpen ? "500px" : "0px",
overflow: "hidden",
transition: "max-height 0.3s ease",
}}
>
{children}
</div>
);
}28. đ¤ Custom Fonts
Custom fonts can be loaded via @font-face, a package like @fontsource, or a <link> tag pointing to a font service such as Google Fonts.
fonts.css
@font-face {
font-family: "Inter";
src: url("/fonts/Inter.woff2") format("woff2");
font-weight: 400;
}
body {
font-family: "Inter", sans-serif;
}29. 𧡠Icons
Icons are commonly used as React components from libraries like lucide-react or react-icons, giving you full control over size, color, and stroke via props â unlike static image icons.
IconExample.jsx
import { Heart } from "lucide-react";
function LikeButton() {
return <Heart size={20} color="#dc2626" />;
}30. đŧī¸ Images & Backgrounds
Images can be rendered with a standard <img> tag, or applied as CSS backgrounds when used decoratively.
Hero.jsx
function Hero() {
return (
<div
style={{
backgroundImage: "url(/images/hero-bg.jpg)",
backgroundSize: "cover",
backgroundPosition: "center",
height: "400px",
}}
/>
);
}Tip
31. đ Styling Best Practices
- Colocate styles with the component they belong to
- Centralize design tokens (colors, spacing, typography) in a shared theme
- Prefer scoped styling (CSS Modules or CSS-in-JS) over global class names in larger apps
- Keep dynamic, computed styles separate from static, reusable ones
- Pick one primary styling approach per project to avoid a mix of conflicting patterns
32. ⥠Performance Optimization
Styling choices can meaningfully affect runtime performance, especially at scale.
- Runtime CSS-in-JS libraries re-compute styles on every render unless memoized â extract static styles outside the component body
- Utility-first frameworks like Tailwind ship pre-purged, static CSS, avoiding runtime style generation entirely
- Avoid inline style objects created fresh on every render for large lists â they defeat memoization
- Lazy-load large component libraries' unused modules to reduce bundle size
33. đˇ TypeScript with Styling
Typing theme objects and styled-component props catches typos â like a missing color key â at compile time.
theme.d.ts
interface Theme {
colors: {
primary: string;
danger: string;
};
}
declare module "styled-components" {
export interface DefaultTheme extends Theme {}
}TypedButton.tsx
const Button = styled.button<{ primary?: boolean }>`
background: ${(props) => (props.primary ? props.theme.colors.primary : "gray")};
`;34. â ī¸ Common Mistakes
- Mixing too many styling approaches (e.g. Tailwind and Styled Components) in the same project
- Writing overly specific, deeply nested CSS selectors that become fragile and hard to override
- Forgetting that CSS Modules require styles.className access, not a plain string
- Creating new inline style objects inside frequently re-rendered lists, hurting performance
- Hard-coding colors instead of referencing theme tokens, making dark mode or rebranding painful later
Danger
35. đŦ Frequently Asked Questions
Which styling approach should I choose for a new project?
For small projects, plain CSS or CSS Modules are often enough. For design-system-heavy apps, a component library like Material UI or Chakra UI saves time. For teams that want speed and consistency without custom CSS, Tailwind CSS is extremely popular.
Is CSS-in-JS slower than plain CSS?
Some runtime CSS-in-JS libraries add a small overhead since styles are generated in the browser. Newer zero-runtime alternatives and utility-first frameworks like Tailwind avoid this by generating static CSS at build time.
Can I combine Tailwind with a component library?
Yes â many teams use Tailwind for layout and spacing alongside a component library like Chakra UI or Material UI for pre-built, accessible widgets, though some visual conflicts can arise and should be tested carefully.