Chapter 17 of 20 · React for Beginners — Complete Free Tutorial

Styling React Applications

Components render structure; styling decides how it feels. The React for Beginners series stays deliberately tool-agnostic here: React does not care how you apply CSS, so you can choose plain stylesheets, CSS Modules, or CSS-in-JS — and the skills from Flexbox, Grid and responsive design all transfer directly. This chapter covers the mainstream options, the JSX-specific rules, and conditional classes.

Your options at a glance

ApproachHow it worksBest for
Plain CSS filesImport a .css; use classNameSmall apps, teams who know CSS
CSS Modulesstyles.card — class names scoped per fileMedium apps; avoids name collisions
Utility frameworksTailwind-style classes in classNameFast iteration with design constraints
CSS-in-JS / styles objectsStyles written in JS, applied inlineHighly dynamic, prop-driven styling

None is "more React" than the others. Start with plain CSS or Modules; reach for dynamic styles only when a value genuinely depends on runtime data.

The JSX rules

Three differences from HTML bite every beginner at least once:

  • class becomes className — class is a reserved JavaScript word, so JSX uses the DOM property name instead.
  • CSS property names are camelCase — in style objects, background-color is backgroundColor.
  • Style values are strings — mostly: { fontSize: '1.25rem' }. Numbers get px appended automatically for length properties (except unitless ones like zIndex, fontWeight).
<div className="card">Plain CSS file</div>

<div
  style={{
    backgroundColor: '#101828',
    padding: 16,            // → '16px'
    borderRadius: 8,
  }}
>
  Inline style object
</div>

Expected output: two panels — the first styled by your stylesheet via .card, the second by the inline object with a dark background and 16 pixels of padding.

Plain CSS with className

The simplest setup: a stylesheet imported once (Vite bundles it), classes referenced by name:

/* src/styles/card.css */
.card {
  border: 1px solid #e4e7ec;
  border-radius: 12px;
  padding: 1.25rem;
  box-shadow: 0 1px 2px rgb(16 24 40 / 6%);
}

.card--highlight {
  border-color: #7a5af8;
}
import '../styles/card.css';

<div className="card card--highlight">Featured</div>

Expected output: a rounded, shadowed card with a violet border — identical markup to the plain HTML you already write; only the attribute name changed.

CSS Modules for scoping

Global class names collide as apps grow (.header means different things on different pages). CSS Modules sidestep that: name the file *.module.css, import it, and access classes as properties — the build step renames them to unique hashes:

import styles from './ProfileCard.module.css';

<div className={styles.card}>
  <img className={styles.avatar} src={avatarUrl} alt="" />
</div>

Expected output: the same visuals, but the compiled class names are unique (something like _card_x3f2a) — no accidental overrides, and an undefined key gives undefined rather than silently styling the wrong element. Compose variants with clsx-style helpers or template strings when needed.

Conditional classes

UI state rarely maps to a static class list. Three idioms, all built on expressions you already know:

const [open, setOpen] = useState(false);

// 1. ternary
<div className={open ? 'panel is-open' : 'panel'}>

// 2. logical and
<button className={busy && 'is-loading'}>Save</button>

// 3. array + filter + join (handy for several flags)
<div className={['tab', active && 'is-active', large && 'is-large']
  .filter(Boolean).join(' ')}>

Expected output: the element carries exactly the classes matching current state — panel is-open when open, plain panel when not — recomputed on every render with no imperative classList calls anywhere.

Dynamic style objects

When a style value comes from data, use an object — the classic example is a progress or result meter:

function ProgressBar({ value }) {
  return (
    <div className="progress">
      <div
        className="progress__fill"
        style={{ width: `${value}%` }}
        role="progressbar"
        aria-valuenow={value}
      ></div>
    </div>
  );
}

Expected output: a bar filled to value percent — e.g. value={70} renders a 70%-wide fill. Values interpolate as strings; remember unitless properties and camelCase keys.

CSS variables still work

Theming does not require CSS-in-JS. Define custom properties in a stylesheet and toggle a class on a root element — the technique from your CSS chapters applies unchanged inside components, and the current value can be read in JS with getComputedStyle when you truly need it.

Common mistakes

  • Writing class= in JSX — React ignores it entirely; the style never applies and no warning appears.
  • String styles — style="color: red" is invalid; pass an object with camelCase keys.
  • Unit confusion — fontSize: 16 becomes 16px, but percentages, rems and viewport units must be strings ('50%', '1.5rem').
  • Global class collisions — two components styling .container differently; scope with CSS Modules or prefix names.
  • Mixing inline styles with classes inconsistently — keep classes for structure and design tokens; reserve inline styles for values that actually change at runtime.

Practice checklist

  1. Move one inline-styled element into a real stylesheet using className.
  2. Create a *.module.css card and confirm the hashed class name in dev tools.
  3. Build a toggle that switches a class with a ternary; verify both states in the DOM.
  4. Render a progress bar driven by a style object and test values 0, 50 and 100.
  5. Add a dark-mode class at the root and re-style one component with CSS variables.

Fast components are not just about CSS — they are about render discipline. Next: Memoizing with useMemo and useCallback.

Quiz: Styling React Applications

5 questions · 70% to pass

Frequently Asked Questions

React is a JavaScript library for building user interfaces from reusable components. It manages how your UI updates when data changes, so you describe what the screen should look like and React performs the efficient DOM updates. It is one of the most in-demand frontend skills and powers products from Netflix to Instagram, which makes it an excellent next step after core JavaScript.

You should be comfortable with functions, arrays and objects (map, filter, spread), destructuring, modules and basic async/await. React itself is written in plain JavaScript — JSX is a syntax layer on top. If any of those feel shaky, revisit the JavaScript fundamentals first and this series will go much faster.

React is a library focused only on the UI layer — you choose your own router, data layer and tooling. Angular is a full framework that ships routing, forms, HTTP client and testing conventions as one opinionated package. React's flexibility and huge ecosystem make it the common choice for teams that want to compose their own stack.

A component is a reusable function that returns UI. It receives input through props, keeps private data in state, and can render other components. Pages are composed by nesting small components (navbar, card list, footer) instead of one giant file, which keeps code testable and easy to change.

JSX is a syntax extension that looks like HTML but compiles to React's create() calls — it is a description of UI, not markup the browser parses. Small differences matter: class becomes className, style takes an object, and you embed JavaScript expressions inside curly braces. Once compiled, React turns that description into real DOM nodes.

Props are inputs passed from a parent — they are read-only inside the child, like function arguments. State is private, mutable data owned by a component; updating it triggers a re-render. The rule of thumb: values that come from outside the component are props; values that change over the component's lifetime are state.

Rendering means calling your component function to produce a description of the UI for the current state and props. It does not always mean DOM work — React compares the new description with the previous one (reconciliation) and applies only the differences to the real DOM. This is why updating state 100 times does not mean 100 full page rebuilds.

An effect runs after the component has committed to the screen — after render, not during it. You list dependencies in an array, and React re-runs the effect only when one of them changes. If the effect returns a cleanup function, React runs it before the next re-run and on unmount, which is where you cancel timers, abort fetches and remove listeners.

Use Vite. Create React App is effectively in maintenance mode, while Vite starts instantly (native ES modules) and builds fast, and it is the default choice for new React projects. This series uses Vite for every example — you only need Node.js installed to follow along.

React apps compile to static files (HTML, CSS and JavaScript), so any static host works: Netlify, Vercel, GitHub Pages or Cloudflare Pages. Run the production build, upload the dist folder, and configure a single-page-application fallback so unknown paths load index.html — routing chapters explain why that fallback is required.