React components are styled with regular CSS. Give elements a class with the className prop and write the rules in a stylesheet, use the style prop for values that come from JavaScript, and reach for CSS Modules or Tailwind when a project grows. The example below defines its CSS in a <style> element so it can run on this page; in a project it would live in a .css file.
Change #2563eb to #16a34a in the .btn rule and the button turns green. Everything you know about CSS applies: selectors, media queries, hover states, animations.
className, not class
JSX compiles to JavaScript, where class is a reserved word, so React uses className, the name the DOM itself uses for that property (element.className). The browser still receives an ordinary class attribute. The same rename applies to htmlFor instead of for on labels. React 19 accepts class with a warning in development, but className is the correct name and the one every codebase uses.
className takes a string, so several classes are space separated: className="btn btn-primary". Because the value is JavaScript, it can be computed.
Conditional classes
To add a class only in some cases, build the string with a ternary or a template literal. A tab bar that highlights the selected tab is the classic case.
Click Lessons and the underline moves, because only that button's string contains active now. Reviews gets the disabled class and the disabled attribute, so it is grey and ignores clicks. The template literal leaves extra spaces when a condition is false, which browsers ignore. When a component has many conditional classes, an array is tidier:
const classes = ['tab', isActive && 'active', isDisabled && 'disabled']
.filter(Boolean)
.join(' ');
Many projects use the tiny clsx package for the same job: clsx('tab', { active: isActive, disabled: isDisabled }).
The style prop
The style prop takes a JavaScript object, not a string. That is why it has double braces: the outer pair opens a JavaScript expression, the inner pair is the object literal. Property names are camelCase (backgroundColor, fontSize, borderRadius), and values are strings or numbers. A number on a length property becomes pixels, so padding: 12 means 12px.
Inline styles shine when a value is computed at render time, such as a progress bar's width.
Click Add 10% until the bar fills and it turns green. Note width: percent + '%': a bare number would mean pixels, so units other than px go in a string. Properties that have no unit stay plain numbers, like opacity: 0.8, zIndex: 2, flexGrow: 1 and lineHeight: 1.5 (which is a multiplier, not pixels).
Inline styles have limits. They cannot express :hover, :focus, media queries or keyframe animations, and each element carries its own copy of the rules. Use them for the dynamic values and keep the rest in CSS.
CSS variables from props
A good middle ground is to set a CSS custom property in style and use it from your stylesheet. The rules stay in CSS, hover states included, and only the value comes from JavaScript.
Pick a new color and both buttons follow it, and the hover fill still works because it is plain CSS. Custom property names keep their dashes in the style object, so they go in quotes: { '--accent': accent }.
CSS files
In a real project you write styles in .css files and import them from the component that needs them. With Vite, the import is enough: the build tool sees import './Button.css', adds the stylesheet to the page during development, and bundles it into a CSS file for production. There is no export to use; the import exists only for its side effect.
// Button.jsx
import './Button.css';
export default function Button({ children }) {
return <button className="button">{children}</button>;
}
/* Button.css */
.button {
background: #2563eb;
color: white;
border-radius: 6px;
}
The catch is that every imported stylesheet is global. A .button rule in Button.css styles every element with that class, anywhere in the app, and two files that both define .title overwrite each other. Teams handle this with naming conventions like BEM (.card__title), or with CSS Modules.
CSS Modules
A file ending in .module.css is a CSS Module. Importing it gives you an object that maps each class name you wrote to a unique generated name, so a class can only apply where you use it. Vite and Next.js support this with no setup.
// Card.jsx
import styles from './Card.module.css';
export default function Card({ title, children, featured }) {
return (
<section className={featured ? `${styles.card} ${styles.featured}` : styles.card}>
<h3 className={styles.title}>{title}</h3>
{children}
</section>
);
}
/* Card.module.css */
.card { border: 1px solid #ddd; border-radius: 10px; padding: 16px; }
.featured { border-color: #2563eb; }
.title { margin: 0; }
In the browser the class comes out as something like _card_1x2ab_1. Another component can define its own .title and the two never collide. You write normal CSS, hover rules and media queries included, which is why CSS Modules are a common default for component styles.
Tailwind CSS with React
Tailwind takes a different approach: instead of writing rules, you compose small utility classes directly in className. px-4 sets horizontal padding, rounded-lg rounds the corners, hover:bg-blue-700 changes the background on hover. Tailwind scans your files and generates CSS only for the classes you use.
Setting it up in a Vite React app (Tailwind v4) takes three steps:
npm install tailwindcss @tailwindcss/vite
// vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import tailwindcss from '@tailwindcss/vite';
export default defineConfig({
plugins: [react(), tailwindcss()],
});
/* src/index.css, imported from main.jsx */
@import "tailwindcss";
Then style components with classes:
function UpgradeCard({ active }) {
return (
<div className="max-w-xs rounded-xl border border-gray-200 p-4">
<h3 className="mb-2 text-lg font-semibold">Pro plan</h3>
<button
className={`rounded-md px-4 py-2 text-white ${active ? 'bg-green-600' : 'bg-blue-600 hover:bg-blue-700'}`}
>
{active ? 'Active' : 'Upgrade'}
</button>
</div>
);
}
Tailwind is not available in the editors on this page, so it is shown as static code. Conditional classes work exactly as in the tab example above, but write each class name in full: Tailwind finds classes by scanning your source text, so a name assembled at runtime such as `bg-${color}-600` is never generated.
When to use which
- Plain CSS files suit small apps and global styles (resets, fonts, page layout). Watch for class name clashes as the app grows.
- CSS Modules suit most component styles: normal CSS, scoped automatically, no extra library.
- Tailwind suits teams that prefer styling in the markup and want a shared design scale for spacing and color.
- The
styleprop is for values computed at render time: a width, a position, a user's chosen color. Pair it with CSS variables to keep hover and media rules in CSS.
You can mix them. A typical app has one global stylesheet, CSS Modules or Tailwind for components, and a few inline styles for dynamic values. CSS-in-JS libraries such as styled-components and Emotion are another option you will meet in older codebases. The page on installing React shows how to start a Vite project where all of these work.
Frequently Asked Questions
Why does React use className instead of class?
JSX compiles to JavaScript, and class is a reserved word there. React uses the DOM property name, className, which is also what element.className is called in plain JavaScript. It still becomes a normal class attribute in the HTML.
How do I write inline styles in React?
Pass an object to the style prop, with camelCase property names: style={{ backgroundColor: 'teal', fontSize: 18 }}. The outer braces open a JavaScript expression and the inner braces are the object. Numbers become pixels for properties that take a length.
How do I add a class conditionally in React?
Build the string in JavaScript: className={isActive ? 'tab active' : 'tab'} or a template literal such as className={`tab ${isActive ? 'active' : ''}`}. For many conditions, a small helper like the clsx package keeps it readable.
Are CSS files in React global?
Yes. import './App.css' adds the stylesheet to the whole page, so its class names can clash with other files. CSS Modules (App.module.css) rename each class to a unique name so it applies only where you import it.
How do I use Tailwind with React?
In a Vite project, install tailwindcss and @tailwindcss/vite, add the plugin to vite.config.js, and put @import "tailwindcss"; at the top of your main CSS file. Then write utility classes in className, such as className="px-4 py-2 rounded bg-blue-600 text-white".