# Styling Components

Course: [React](https://www.devtutorials.ai/learn/react) · Section: Application Quality
Source: https://www.devtutorials.ai/learn/react/quality/styling-components

> CSS in React: importing CSS files, className, multiple classes, inline styles, dynamic and conditional styling, and CSS Modules with scoped class names.

## CSS in React

React apps use normal CSS. The browser does not care that the page was made with React. It still reads CSS rules and paints the page.

What changes is **where** you add the styles. In React, you style the elements that your components return. You can use a CSS file, a style object, or a CSS Module. This lesson shows all three.

### Real-World Example

A bookshop app has a `Button` component used on 15 pages. Its styles are in `Button.module.css`, so the class `.primary` gets a unique name and cannot clash with another file's `.primary`.

When a book is out of stock, the card adds the class `soldOut` with `className={book.stock === 0 ? styles.soldOut : ""}`, and the card turns grey.

<figure className="react-fig">
<svg viewBox="0 0 520 120" role="img" aria-labelledby="fig-s-flow" style={{ width: "100%", maxWidth: 520, height: "auto" }}>
  <title id="fig-s-flow">A CSS file is imported once, its classes are used with className, and the browser paints the styled page</title>
  <rect x="5" y="30" width="130" height="56" rx="8" style={{ fill: "var(--panel2)", stroke: "var(--faint)", strokeWidth: 1.5 }} />
  <text x="70.0" y="55.0" textAnchor="middle" style={{ fill: "var(--ink2)", fontSize: 13, fontWeight: 700, fontFamily: "var(--mono)" }}>styles.css</text>
  <text x="70.0" y="72.0" textAnchor="middle" style={{ fill: "var(--ink2)", fontSize: 11, fontFamily: "var(--sans)" }}>.card &#123; ... &#125;</text>
  <path d="M135 58 L165 58" style={{ stroke: "var(--faint)", strokeWidth: 2, fill: "none" }} />
  <path d="M165 58 L156.7 61.5 L156.7 54.5 Z" style={{ fill: "var(--faint)" }} />
  <rect x="170" y="30" width="160" height="56" rx="8" style={{ fill: "var(--accent-secondary-soft)", stroke: "var(--accent-secondary)", strokeWidth: 1.5 }} />
  <text x="250.0" y="55.0" textAnchor="middle" style={{ fill: "var(--accent-secondary)", fontSize: 13, fontWeight: 700, fontFamily: "var(--mono)" }}>App.tsx</text>
  <text x="250.0" y="72.0" textAnchor="middle" style={{ fill: "var(--ink2)", fontSize: 11, fontFamily: "var(--sans)" }}>import "./styles.css"</text>
  <path d="M330 58 L360 58" style={{ stroke: "var(--faint)", strokeWidth: 2, fill: "none" }} />
  <path d="M360 58 L351.7 61.5 L351.7 54.5 Z" style={{ fill: "var(--faint)" }} />
  <rect x="365" y="30" width="150" height="56" rx="8" style={{ fill: "var(--ok-soft)", stroke: "var(--ok)", strokeWidth: 1.5 }} />
  <text x="440.0" y="55.0" textAnchor="middle" style={{ fill: "var(--ok)", fontSize: 13, fontWeight: 700, fontFamily: "var(--mono)" }}>Page</text>
  <text x="440.0" y="72.0" textAnchor="middle" style={{ fill: "var(--ink2)", fontSize: 11, fontFamily: "var(--sans)" }}>class="card"</text>
  <text x="150" y="108" textAnchor="middle" style={{ fill: "var(--muted)", fontSize: 11, fontWeight: 400, fontFamily: "var(--sans)" }}>import</text>
  <text x="345" y="108" textAnchor="middle" style={{ fill: "var(--muted)", fontSize: 11, fontWeight: 400, fontFamily: "var(--sans)" }}>className</text>
  <circle r="6" style={{ fill: "var(--accent-secondary)" }}>
    <animateMotion dur="2.4s" repeatCount="indefinite" path="M135 58 L165 58 M330 58 L360 58" />
  </circle>
</svg>
<figcaption>Import the file once. Then any component can use its class names.</figcaption>
</figure>

## Importing CSS Files

Write your styles in a `.css` file. Then import that file in a component with one line:

```tsx
import "./styles.css";
```

This line has no name before `from`. It does not give you a value. It only tells the build tool to add the CSS to the page. The rules then work for the whole page, not only for that component.

Open the `styles.css` tab below, change the colour, and click Run.

      <h2 className="title">The Alchemist</h2>
      <p className="author">Paulo Coelho</p>
      <p className="price">₹299</p>
    </div>
  );
}
`,
    "styles.css": `.card {
  border: 1px solid #ccc;
  border-radius: 10px;
  padding: 16px;
  max-width: 260px;
  font-family: sans-serif;
}

.title {
  margin: 0 0 4px;
  color: #2b59c3;
}

.author {
  color: #666;
  margin: 0;
}

.price {
  font-weight: bold;
  font-size: 20px;
}
`,
    "main.tsx": `import { createRoot } from "react-dom/client";

createRoot(document.getElementById("root")!).render(<App />);
`,
  }}
/>

## ClassName Usage

In HTML you write `class="card"`. In React you write `className="card"`.

The reason is simple. TSX is turned into JavaScript, and `class` is a keyword in JavaScript (it creates classes). So React uses `className` for the CSS class. React puts it on the page as a normal `class`.

```tsx
<div className="card">...</div>
```

If you write `class` instead of `className`, React prints a warning in the console. Always use `className` in TSX.

## Applying Multiple Classes

An element can have more than one class. Put the names in one string, with a space between them:

```tsx
<div className="card featured">
```

Here `card` gives the basic box. `featured` adds a gold border on top. Each class adds its own styles.

<figure className="react-fig">
<svg viewBox="0 0 520 150" role="img" aria-labelledby="fig-s-multi" style={{ width: "100%", maxWidth: 520, height: "auto" }}>
  <title id="fig-s-multi">Three classes on one element: card gives the box, featured adds a border, sale adds a red price</title>
  <text x="260" y="22" textAnchor="middle" style={{ fill: "var(--ink2)", fontSize: 13, fontWeight: 700, fontFamily: "var(--mono)" }}>className="card featured sale"</text>
  <rect x="10" y="40" width="150" height="50" rx="8" style={{ fill: "var(--panel2)", stroke: "var(--faint)", strokeWidth: 1.5 }} />
  <text x="85.0" y="62.0" textAnchor="middle" style={{ fill: "var(--ink2)", fontSize: 13, fontWeight: 700, fontFamily: "var(--mono)" }}>card</text>
  <text x="85.0" y="79.0" textAnchor="middle" style={{ fill: "var(--ink2)", fontSize: 11, fontFamily: "var(--sans)" }}>white box, padding</text>
  <rect x="185" y="40" width="150" height="50" rx="8" style={{ fill: "var(--accent-secondary-soft)", stroke: "var(--accent-secondary)", strokeWidth: 1.5 }} />
  <text x="260.0" y="62.0" textAnchor="middle" style={{ fill: "var(--accent-secondary)", fontSize: 13, fontWeight: 700, fontFamily: "var(--mono)" }}>featured</text>
  <text x="260.0" y="79.0" textAnchor="middle" style={{ fill: "var(--ink2)", fontSize: 11, fontFamily: "var(--sans)" }}>gold border</text>
  <rect x="360" y="40" width="150" height="50" rx="8" style={{ fill: "var(--bad-soft)", stroke: "var(--bad)", strokeWidth: 1.5 }} />
  <text x="435.0" y="62.0" textAnchor="middle" style={{ fill: "var(--bad)", fontSize: 13, fontWeight: 700, fontFamily: "var(--mono)" }}>sale</text>
  <text x="435.0" y="79.0" textAnchor="middle" style={{ fill: "var(--ink2)", fontSize: 11, fontFamily: "var(--sans)" }}>red price</text>
  <path d="M85 90 L240 118" style={{ stroke: "var(--faint)", strokeWidth: 2, fill: "none" }} />
  <path d="M240 118 L231.2 120.0 L232.5 113.1 Z" style={{ fill: "var(--faint)" }} />
  <path d="M260 90 L260 116" style={{ stroke: "var(--faint)", strokeWidth: 2, fill: "none" }} />
  <path d="M260 116 L256.5 107.7 L263.5 107.7 Z" style={{ fill: "var(--faint)" }} />
  <path d="M435 90 L280 118" style={{ stroke: "var(--faint)", strokeWidth: 2, fill: "none" }} />
  <path d="M280 118 L287.5 113.1 L288.8 120.0 Z" style={{ fill: "var(--faint)" }} />
  <rect x="190" y="118" width="140" height="28" rx="8" style={{ fill: "var(--ok-soft)", stroke: "var(--ok)", strokeWidth: 1.5 }} />
  <text x="260.0" y="137.0" textAnchor="middle" style={{ fill: "var(--ok)", fontSize: 13, fontWeight: 700, fontFamily: "var(--sans)" }}>all three together</text>
  <rect x="10" y="40" width="150" height="50" rx="8" style={{ fill: "none", stroke: "var(--accent-secondary)", strokeWidth: 3 }} opacity="0">
    <animate attributeName="opacity" values="0;1;0;0" keyTimes="0;0.15;0.4;1" dur="4s" begin="0s" repeatCount="indefinite" />
  </rect>
  <rect x="185" y="40" width="150" height="50" rx="8" style={{ fill: "none", stroke: "var(--accent-secondary)", strokeWidth: 3 }} opacity="0">
    <animate attributeName="opacity" values="0;1;0;0" keyTimes="0;0.15;0.4;1" dur="4s" begin="0.6s" repeatCount="indefinite" />
  </rect>
  <rect x="360" y="40" width="150" height="50" rx="8" style={{ fill: "none", stroke: "var(--accent-secondary)", strokeWidth: 3 }} opacity="0">
    <animate attributeName="opacity" values="0;1;0;0" keyTimes="0;0.15;0.4;1" dur="4s" begin="1.2s" repeatCount="indefinite" />
  </rect>
</svg>
<figcaption>Classes are joined with spaces. Each one adds its own styles.</figcaption>
</figure>

Sometimes one class is needed only in some cases. Build the string from parts. In the code below, this line adds `sale` only for books on sale:

```tsx
const classes = ["card", book.onSale ? "sale" : ""].join(" ");
```

`.join(" ")` joins the list with spaces, so the result is `"card sale"` or `"card "`. An extra space does no harm.

      {books.map((book) => {
        const classes = [
          "card",
          book.featured ? "featured" : "",
          book.onSale ? "sale" : "",
        ].join(" ");
        console.log(book.title + " -> " + classes);
        return (
          <div key={book.title} className={classes}>
            <strong>{book.title}</strong>
            <p className="price">₹{book.price}</p>
          </div>
        );
      })}
    </div>
  );
}
`,
    "styles.css": `.shelf { display: flex; gap: 10px; font-family: sans-serif; }
.card { border: 1px solid #ccc; border-radius: 8px; padding: 10px; width: 140px; }
.featured { border: 2px solid goldenrod; }
.sale .price { color: crimson; }
`,
    "main.tsx": `import { createRoot } from "react-dom/client";

createRoot(document.getElementById("root")!).render(<App />);
`,
  }}
/>

## Inline Styling

You can also put styles right on an element with the `style` prop. In React, `style` takes a **JavaScript object**, not a text string.

```tsx
<p style={{ color: "green", fontSize: 18 }}>In stock</p>
```

The outer `{ }` means "here comes JavaScript". The inner `{ }` is the object. Notice two more things:

- CSS names with a dash become camelCase: `font-size` becomes `fontSize`, and `background-color` becomes `backgroundColor`.
- A plain number means pixels. `fontSize: 18` is the same as `"18px"`.

To type a style object in TypeScript, use `CSSProperties` from React:

      <h2 style={{ margin: 0, color: "#2b59c3" }}>Wings of Fire</h2>
      <p style={{ color: "green", fontSize: 18 }}>In stock</p>
      <p style={{ backgroundColor: "#fff3cd", padding: 6 }}>Only 2 left!</p>
    </div>
  );
}
`,
    "main.tsx": `import { createRoot } from "react-dom/client";

createRoot(document.getElementById("root")!).render(<App />);
`,
  }}
/>

Use a CSS file for most styles. Use inline styles for small values that are worked out while the app runs, such as a width from a number. Inline styles cannot do `:hover` or media queries.

## Dynamic and Conditional Styling

**Dynamic styling** means the style changes when the data changes. Because styles are written in your component, you can use state and props to choose them.

There are two common ways:

- **Change the class.** `className={isSelected ? "book selected" : "book"}` adds the `selected` class only when `isSelected` is true.
- **Change an inline value.** `style={{ width: percent + "%" }}` sets the width from a number.

<figure className="react-fig">
<svg viewBox="0 0 520 120" role="img" aria-labelledby="fig-s-toggle" style={{ width: "100%", maxWidth: 520, height: "auto" }}>
  <title id="fig-s-toggle">Dynamic styling: when state changes, the class name changes, so the look changes</title>
  <rect x="10" y="35" width="140" height="50" rx="8" style={{ fill: "var(--accent-soft)", stroke: "var(--accent)", strokeWidth: 1.5 }} />
  <text x="80.0" y="57.0" textAnchor="middle" style={{ fill: "var(--accent)", fontSize: 13, fontWeight: 700, fontFamily: "var(--mono)" }}>isSelected</text>
  <text x="80.0" y="74.0" textAnchor="middle" style={{ fill: "var(--ink2)", fontSize: 11, fontFamily: "var(--sans)" }}>true or false</text>
  <path d="M150 60 L185 60" style={{ stroke: "var(--faint)", strokeWidth: 2, fill: "none" }} />
  <path d="M185 60 L176.7 63.5 L176.7 56.5 Z" style={{ fill: "var(--faint)" }} />
  <rect x="190" y="35" width="150" height="50" rx="8" style={{ fill: "var(--accent-secondary-soft)", stroke: "var(--accent-secondary)", strokeWidth: 1.5 }} />
  <text x="265.0" y="57.0" textAnchor="middle" style={{ fill: "var(--accent-secondary)", fontSize: 13, fontWeight: 700, fontFamily: "var(--mono)" }}>className</text>
  <text x="265.0" y="74.0" textAnchor="middle" style={{ fill: "var(--ink2)", fontSize: 11, fontFamily: "var(--sans)" }}>"book selected"</text>
  <path d="M340 60 L375 60" style={{ stroke: "var(--faint)", strokeWidth: 2, fill: "none" }} />
  <path d="M375 60 L366.7 63.5 L366.7 56.5 Z" style={{ fill: "var(--faint)" }} />
  <rect x="380" y="35" width="130" height="50" rx="8" style={{ fill: "var(--panel2)", stroke: "var(--faint)", strokeWidth: 1.5 }}>
    <animate attributeName="fill" values="var(--panel2);var(--accent-soft);var(--accent-soft);var(--panel2)" keyTimes="0;0.1;0.5;0.6" dur="4s" repeatCount="indefinite" />
  </rect>
  <text x="445" y="65" textAnchor="middle" style={{ fill: "var(--ink2)", fontSize: 12, fontWeight: 700, fontFamily: "var(--sans)" }}>The Alchemist</text>
  <circle r="6" style={{ fill: "var(--accent-secondary)" }}>
    <animateMotion dur="4s" repeatCount="indefinite" path="M150 60 L185 60 M340 60 L375 60" />
  </circle>
</svg>
<figcaption>Change the data, and React updates the class for you.</figcaption>
</figure>

Click a book below. `selectedId` changes, so the class changes, so the colour changes. The stock bar uses an inline width, and turns red when stock is low.

      {books.map((book) => (
        <li
          key={book.id}
          className={book.id === selectedId ? "book selected" : "book"}
          onClick={() => setSelectedId(book.id)}
        >
          {book.title}
          <div className="bar">
            <div
              style={{
                width: book.stock * 8 + "%",
                backgroundColor: book.stock < 5 ? "crimson" : "seagreen",
              }}
            />
          </div>
        </li>
      ))}
    </ul>
  );
}
`,
    "styles.css": `.list { list-style: none; padding: 0; max-width: 280px; font-family: sans-serif; }
.book { padding: 8px 10px; border: 1px solid #ddd; border-radius: 6px; margin-bottom: 6px; cursor: pointer; }
.selected { background: #e6efff; border-color: #2b59c3; }
.bar { height: 6px; background: #eee; border-radius: 3px; margin-top: 6px; }
.bar div { height: 6px; border-radius: 3px; }
`,
    "main.tsx": `import { createRoot } from "react-dom/client";

createRoot(document.getElementById("root")!).render(<App />);
`,
  }}
/>

## CSS Modules

### What are CSS Modules

A normal CSS file is **global**. If two files both have a `.title` class, the rules mix, and the last one wins. In a big app, this causes surprise bugs.

A **CSS Module** is a CSS file whose name ends with `.module.css`, such as `Card.module.css`. You import it with a name:

```tsx
import styles from "./Card.module.css";
```

`styles` is an object. Each class in the file becomes a key. You use it like this: `className={styles.title}`.

<figure className="react-fig">
<svg viewBox="0 0 520 190" role="img" aria-labelledby="fig-s-modules" style={{ width: "100%", maxWidth: 520, height: "auto" }}>
  <title id="fig-s-modules">CSS Modules give each file its own class names, so two .title classes never clash</title>
  <rect x="10" y="15" width="160" height="46" rx="8" style={{ fill: "var(--accent-soft)", stroke: "var(--accent)", strokeWidth: 1.5 }} />
  <text x="90.0" y="35.0" textAnchor="middle" style={{ fill: "var(--accent)", fontSize: 13, fontWeight: 700, fontFamily: "var(--mono)" }}>Card.module.css</text>
  <text x="90.0" y="52.0" textAnchor="middle" style={{ fill: "var(--ink2)", fontSize: 11, fontFamily: "var(--sans)" }}>.title</text>
  <rect x="10" y="120" width="160" height="46" rx="8" style={{ fill: "var(--accent-secondary-soft)", stroke: "var(--accent-secondary)", strokeWidth: 1.5 }} />
  <text x="90.0" y="140.0" textAnchor="middle" style={{ fill: "var(--accent-secondary)", fontSize: 13, fontWeight: 700, fontFamily: "var(--mono)" }}>Header.module.css</text>
  <text x="90.0" y="157.0" textAnchor="middle" style={{ fill: "var(--ink2)", fontSize: 11, fontFamily: "var(--sans)" }}>.title</text>
  <path d="M170 38 L250 38" style={{ stroke: "var(--faint)", strokeWidth: 2, fill: "none" }} />
  <path d="M250 38 L241.7 41.5 L241.7 34.5 Z" style={{ fill: "var(--faint)" }} />
  <path d="M170 143 L250 143" style={{ stroke: "var(--faint)", strokeWidth: 2, fill: "none" }} />
  <path d="M250 143 L241.7 146.5 L241.7 139.5 Z" style={{ fill: "var(--faint)" }} />
  <rect x="255" y="15" width="255" height="46" rx="8" style={{ fill: "var(--accent-soft)", stroke: "var(--accent)", strokeWidth: 1.5 }} />
  <text x="382.5" y="43.0" textAnchor="middle" style={{ fill: "var(--accent)", fontSize: 13, fontWeight: 700, fontFamily: "var(--mono)" }}>_title_Card_x7k2</text>
  <rect x="255" y="120" width="255" height="46" rx="8" style={{ fill: "var(--accent-secondary-soft)", stroke: "var(--accent-secondary)", strokeWidth: 1.5 }} />
  <text x="382.5" y="148.0" textAnchor="middle" style={{ fill: "var(--accent-secondary)", fontSize: 13, fontWeight: 700, fontFamily: "var(--mono)" }}>_title_Header_9fq1</text>
  <text x="382" y="98" textAnchor="middle" style={{ fill: "var(--ok)", fontSize: 12, fontWeight: 700, fontFamily: "var(--sans)" }}>different names on the page, so no clash</text>
  <circle r="6" style={{ fill: "var(--accent-secondary)" }}>
    <animateMotion dur="2s" repeatCount="indefinite" path="M170 38 L250 38" />
  </circle>
  <circle r="6" style={{ fill: "var(--accent)" }}>
    <animateMotion dur="2s" repeatCount="indefinite" path="M170 143 L250 143" />
  </circle>
</svg>
<figcaption>You write a short name. The build tool turns it into a unique name.</figcaption>
</figure>

### Scoped Styling in CSS Modules

**Scoped** means "works only in one place". The build tool renames every class in a module to a unique name, such as `_title_Card_x7k2`. Only the component that imports the module knows that name. So `.title` in `Card.module.css` can never touch `.title` in `Header.module.css`.

Below, both files use `.title`, but each title keeps its own colour. The console prints the real class names.

    </div>
  );
}
`,
    "Header.tsx": `import styles from "./Header.module.css";

  console.log("Header title class: " + styles.title);
  return <h1 className={styles.title}>Book Corner</h1>;
}
`,
    "Header.module.css": `.title {
  color: white;
  background: #2b59c3;
  padding: 8px 12px;
  font-family: sans-serif;
}
`,
    "Card.tsx": `import styles from "./Card.module.css";

interface CardProps {
  title: string;
  price: number;
}

  console.log("Card title class: " + styles.title);
  return (
    <div className={styles.card}>
      <h2 className={styles.title}>{title}</h2>
      <p>₹{price}</p>
    </div>
  );
}
`,
    "Card.module.css": `.card {
  border: 1px solid #ccc;
  border-radius: 8px;
  padding: 10px;
  max-width: 220px;
  font-family: sans-serif;
}

.title {
  color: darkorange;
  font-size: 18px;
  margin: 0;
}
`,
    "main.tsx": `import { createRoot } from "react-dom/client";

createRoot(document.getElementById("root")!).render(<App />);
`,
  }}
/>

To use two module classes on one element, join them the same way as before: `className={styles.card + " " + styles.featured}`.

Easy Way to Remember: a plain `.css` file is shared by the whole page. A `.module.css` file belongs to one component.

## Common mistakes

<div className="tscroll">

| Mistake | What you see | Fix |
|---|---|---|
| Writing `class="card"` in TSX | A console warning | Use `className` |
| Writing `style="color: red"` | An error: style must be an object | Use `style={{ color: "red" }}` |
| Writing `font-size` in a style object | A syntax error, or the style is ignored | Use camelCase: `fontSize` |
| Two classes as `className="card" className="sale"` | Only one class is used | One string: `className="card sale"` |
| `className="title"` with a CSS Module | No style is applied | Use `className={styles.title}` |
| Naming a module `Card.css` | Classes are global, not scoped | Name it `Card.module.css` |

</div>

## Check Yourself

## Exercises

## What to Remember

- Import a CSS file with `import "./styles.css";` and use `className`, not `class`.
- Join several classes with spaces in one string. Choose classes with conditions to change the look from state.
- Inline `style` takes an object with camelCase names. A `.module.css` file gives scoped class names through `styles.name`.

Next: Error Handling
