Styling in React
Reviewed & published by Brayan K
Styling in React means applying CSS through the className attribute, the style prop's camelCase object, or scoped CSS Modules. You combine classes conditionally with template strings or a small classnames helper, choosing inline styles only for values that must be computed at runtime.
Part of the free React course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
What You'll Learn
- Use className (not class) to apply CSS
- Write inline style objects in camelCase
- Scope styles safely with CSS Modules
- Combine classes conditionally with a helper
- Build dynamic class strings from state
- Know when to reach for utility CSS
1️⃣ className vs class & Inline Style Objects
Apply CSS classes with className (since class is reserved in JavaScript). The style prop takes a JavaScript object, so properties are camelCase (backgroundColor, not background-color) and values are usually strings like "16px".
function Tag({ active }) {
return (
<span
className="tag"
style={{ backgroundColor: active ? "green" : "gray", fontSize: "14px" }}
>
Status
</span>
);
}// Inline style = a JS OBJECT: camelCase keys, string values.
// CSS "background-color: red; font-size: 16px;" becomes:
function buttonStyle(active) {
return {
backgroundColor: active ? "green" : "gray", // camelCase
fontSize: "16px", // string value
padding: "8px 12px",
};
}
console.log(buttonStyle(true).backgroundColor); // green
console.log(buttonStyle(false).backgroundColor); // gray
console.log(buttonStyle(true).fontSize); // 16px2️⃣ CSS Modules & Conditional Classes
CSS Modules scope class names to a single file: you import styles from a .module.css file and reference styles.button, which the build tool turns into a unique hashed name so it never collides. To toggle classes by state, build the string with a template literal or a tiny classnames-style helper that drops falsy parts.
import styles from "./Button.module.css"; // scoped class names
function Button({ primary, children }) {
const cls = [styles.btn, primary && styles.primary]
.filter(Boolean)
.join(" ");
return <button className={cls}>{children}</button>;
}// Conditional classes: build a className string from conditions.
// This is exactly what a "classnames" helper does for you.
function classNames(...parts) {
return parts.filter(Boolean).join(" "); // drop falsy, join with spaces
}
const isActive = true;
const isDisabled = false;
console.log(classNames("btn", isActive && "btn-active", isDisabled && "btn-disabled"));
// btn btn-active
console.log(classNames("card", false, null, "card-lg"));
// card card-lgYour turn — camelCase a property and toggle a class:
// 🎯 YOUR TURN — fill in each ___ then press "Try it Yourself".
// 1) Convert a CSS property name to an inline-style KEY (camelCase):
// "margin-top" -> "marginTop".
// 2) Build a className that always has "tag" plus "tag-on" only when on.
function toCamel(prop) {
return prop.replace(/-(\w)/g, (_, c) => c.___()); // 👉 toUpperCase
}
function tagClass(on) {
return ["tag", on ___ "tag-on"].filter(Boolean).join(" "); // 👉 &&
}
console.log(toCamel("margin-top")); // marginTop
console.log(tagClass(true)); // tag tag-on
console.log(tagClass(false)); // tag
// ✅ Expected output:
// marginTop
// tag tag-on
// tag3️⃣ Choosing an Approach (and Utility CSS)
Default to classes — whether CSS Modules or a utility framework — because they cache well and support pseudo-classes (:hover) and media queries that inline styles cannot. Reserve inline styles for genuinely dynamic values you compute at runtime, like a progress bar width from state. Utility CSS (small single-purpose classes) shines when you want to style directly in markup without writing new CSS files.
// Dynamic value -> inline style is the right tool:
function ProgressBar({ percent }) {
return (
<div className="bar">
<div className="fill" style={{ width: percent + "%" }} />
</div>
);
}
// Static look -> a class (cacheable, supports :hover & media queries):
// <button className="btn btn-primary">Save</button>🧩 Reorder Challenge
These lines build a button that applies a CSS-Module class conditionally. Put them in the right order:
A. const cls = [styles.btn, big && styles.large].filter(Boolean).join(" ");
B. import styles from "./Button.module.css";
C. return <button className={cls}>{label}</button>;
D. function Button({ big, label }) {
E. }Common Errors (and the fix)
- Using class in JSX. — React ignores it and warns. Use className.
- Kebab-case style keys. — { "font-size": 16 } fails. Write { fontSize: "16px" }.
- Single braces on the style prop. — style="..." is wrong; it needs an object: style={{ ... }}.
- Global class collisions. — Two components share a .title class. Scope them with CSS Modules.
📋 Quick Reference
| Want | Use |
|---|---|
| Apply a class | className="btn" |
| Inline style | style={{ fontSize: "16px" }} |
| Scoped class | className={styles.btn} |
| Conditional class | [a, on && b].join(" ") |
| Dynamic value | style={{ width: p + "%" }} |
🧠 Quick Recall
1. How do you write background-color in a style object?
2. What do CSS Modules fix?
Global class-name collisions, by scoping each class to one file with a unique hashed name.
3. When are inline styles the right call?
For dynamic, runtime-computed values like a width from state — not for static, reusable looks.
Mini-Challenge: className Builder
Write one helper that assembles a button's class string from a variant, an optional size, and a disabled flag, dropping anything falsy. Run it and check your output.
// 🎯 MINI-CHALLENGE: a className builder for a button
//
// Write buttonClass({ variant, size, disabled }) that returns a class string:
// 1. Always include "btn".
// 2. Add "btn-" + variant (e.g. "btn-primary").
// 3. Add "btn-" + size only if size is given.
// 4. Add "is-disabled" only when disabled is true.
// (drop any falsy parts, join with spaces)
//
// ✅ Expected output:
// btn btn-primary btn-lg
// btn btn-ghost is-disabled
console.log(/* call 1 */ ""); // replace with buttonClass({ variant: "primary", size: "lg" })
console.log(/* call 2 */ ""); // replace with buttonClass({ variant: "ghost", disabled: true })
// your code here🎉 Lesson Complete
- ✅ Use className, not class
- ✅ Inline style is a camelCase object with string values
- ✅ CSS Modules scope classes so names never clash
- ✅ Build conditional classes with a classnames helper; inline styles only for dynamic values
Practice quiz
Which attribute applies a CSS class to a JSX element?
- class
- cssClass
- className
- styleName
Answer: className. JSX uses className because class is a reserved word in JavaScript. It sets the element's class attribute under the hood.
How do you write background-color in a React inline style object?
- { backgroundColor: "red" }
- { "background-color": "red" }
- { background_color: "red" }
- { BackgroundColor: "red" }
Answer: { backgroundColor: "red" }. The style prop takes a JS object, so properties are camelCase: backgroundColor, not background-color.
What value does the style prop expect?
- A CSS string like "color: red"
- The name of a CSS class
- A URL to a stylesheet
- A JavaScript object, e.g. style={{ fontSize: "16px" }}
Answer: A JavaScript object, e.g. style={{ fontSize: "16px" }}. style takes an object. Note the double braces: the outer is the JSX expression, the inner is the object literal.
What problem do CSS Modules solve?
- They make CSS load faster
- They scope class names to one file so names never collide globally
- They convert CSS to inline styles
- They add vendor prefixes automatically
Answer: They scope class names to one file so names never collide globally. Importing styles from a .module.css file turns styles.button into a unique hashed name at build time, preventing global collisions.
When are inline styles the right choice over a class?
- For genuinely dynamic, runtime-computed values like a width from state
- For every element, to be safe
- For static, reusable looks
- For hover and focus states
Answer: For genuinely dynamic, runtime-computed values like a width from state. Reserve inline styles for computed values (e.g. a progress bar width). Classes cache and support pseudo-classes and media queries that inline styles can't.
Why can't inline styles express :hover or media queries?
- React strips them out
- They require a build step
- Inline styles apply directly to one element and can't represent pseudo-classes or media-query rules
- They only work in production
Answer: Inline styles apply directly to one element and can't represent pseudo-classes or media-query rules. Pseudo-states and breakpoints are CSS rules, which inline styles simply lack. Use classes for hover, focus, and responsive styling.
What does a classnames-style helper like [a, on && b].filter(Boolean).join(" ") do?
- Sorts the class names alphabetically
- Drops falsy parts and joins the truthy class names into one string
- Validates that each class exists in CSS
- Converts kebab-case to camelCase
Answer: Drops falsy parts and joins the truthy class names into one string. filter(Boolean) removes false/null/undefined/empty entries, and join(" ") combines what's left into a space-separated className string.
What's wrong with style="color: red" in JSX?
- Nothing, it's valid
- color must be camelCase
- It should be className
- style needs an object, not a string: style={{ color: "red" }}
Answer: style needs an object, not a string: style={{ color: "red" }}. The style prop in JSX takes an object literal, not a CSS string. Single braces with a string is invalid.
How do you reference a scoped class from a CSS Module named Button.module.css?
- className="btn"
- className={styles.btn} after importing styles from the module
- className={"Button.module.css btn"}
- style={styles.btn}
Answer: className={styles.btn} after importing styles from the module. You import styles from "./Button.module.css" and use className={styles.btn}, which resolves to the unique hashed class name.
Why default to classes rather than inline styles for most styling?
- Classes are required by React
- Inline styles are deprecated
- Classes cache well and support pseudo-classes and media queries that inline styles cannot
- Classes render faster on the server
Answer: Classes cache well and support pseudo-classes and media queries that inline styles cannot. Classes (CSS Modules or utility CSS) are cacheable and support :hover and media queries, so they're the sensible default; inline styles are for dynamic values.
Continue this course
- Previous: Immutable State Updates
- Next: useEffect & the Dependency Array — Side effects, the dependency array, cleanup, and fixing infinite loops
- Quick reference: React cheat sheet
Frequently asked questions
Why className instead of class?
class is a reserved word in JavaScript, and JSX attributes map to DOM properties. So React uses className, which sets the element's class attribute under the hood.
How are inline style objects different from CSS?
The style prop takes a JavaScript object, so properties are camelCase (backgroundColor, not background-color) and most values are strings ('16px'). Numbers get px added for many properties.
What problem do CSS Modules solve?
They scope class names to one file. styles.button becomes a unique hashed name at build time, so two components can both use a .button class without colliding globally.
When should I use inline styles vs classes?
Use classes (CSS Modules or utility CSS) for most styling — they cache and support pseudo-classes and media queries. Reach for inline styles only for truly dynamic, computed values like a width from state.