Where styles live in React?

Each approach has pros/cons. Below: detailed explanations and examples.

Various Approaches

1️. Colocate (Component-first approach)

src/
 ├─ components/
 │   ├─ Home/
 │   │   ├─ Home.tsx
 │   │   └─ Home.module.css

2️. Global / App-wide styles

src/
 ├─ styles/
 │   ├─ index.css        <-- resets, typography
 │   ├─ variables.css    <-- CSS variables
 │   └─ theme.css

3. CSS-in-JS / Styled-components

const Container = styled.div`
  padding: 20px;
  background: lightblue;
`;

4. Hybrid approach

src/
 ├─ components/
 │   ├─ Home/
 │   │   ├─ Home.tsx
 │   │   └─ Home.module.css
 ├─ styles/
 │   ├─ index.css
 │   ├─ variables.css

React usesclassName instead of class but why?

//jsx or tsx file
<button className="btn btn-primary">Click Me</button>

Renders as:
<button class="btn btn-primary">Click Me</button>

Global styling vs Scope (Component) styling

Traditional CSS files included (e.g., index.css, App.css).

/* index.css */
body { font-family: Inter, system-ui; }
.btn { padding: 8px 12px; border-radius: 6px; }
.btn-primary { background: blue; color: white; }


// Home.ts
export default function Home() {
return (
        <button className="btn btn-primary">Primary Button</button>
       )
}

Global Style

Scoped (component) styling

Styles limited to one component (no global leakage). Implemented by CSS Modules, styled-components, or similar.

Helps avoids collisions, improves maintainability, easier component reuse.

There is one file name rule you need to follow: The convention for scoped CSS is to name the file like this:

ComponentName.module.css

The .module.css extension tells your build tool (Vite, Webpack, etc.) to treat that file as a CSS Module for scoping.

Ways to scope:

/* Home.module.css */
.scopedButton {
  background-color: #10b981;
  color: white;
  border: none;
  padding: 0.75rem 1.25rem;
  border-radius: 8px;
  cursor: pointer;
  font-weight: 600;
  transition: background-color 0.2s ease;
}

.scopedButton:hover {
  background-color: #059669;
}


// Home.ts
import styles from './Home.module.css';
export default function Home() {
return (
        <button className={styles.scopedButton}>Scoped Button</button>
        )
}

scoped style

What’s that random string (like __jpbsk_3)?

When you use CSS Modules, your build tool (like Webpack, Vite, or Next.js) automatically renames your CSS class names during compilation.

That suffix __jpbsk_3 is a unique hash automatically generated to prevent class name collisions.

so, "scopedButton" become "._scopedButton_jpbsk_3"

Inline styling (style={{}})

These styles are applied directly to the element’s style attribute in the DOM.

// Home.tsx
<button style={{ backgroundColor: 'Red', color: 'white' }}>
Inline Button
</button>


which renders button as:
// Generated HTML
<button style="background-color: red; color: white;">Inline Button</button>

Use inline styles when the style is dynamic, small, and local to one element.

  1. Dynamic styling based on props/state

//Example 1
<button style={{ backgroundColor: isActive ? 'blue' : 'gray' }}>
  Click Me
</button>


//Example 2
const style = { transform: `translateX(${x}px)` };
<div style={style}>Moving</div>
  1. One-off tweaks

<div style={{ marginTop: '10px' }}>Quick margin fix</div>

Avoid inline styles when you need reusable, scalable, or advanced CSS features.

Applying multiple styles

You often need to combine classes or style objects.

With plain CSS / CSS Modules

<button className={`${styles.btn} ${styles.large} ${styles.rounded}`}>Hi</button>

With global CSS classes

<button className="btn large rounded">Hi</button>

With styled-components: composition

const Primary = styled(Button)`background: blue; color: white;`;

With inline style objects (merging)

const base = { padding: 8, borderRadius: 6 };
const red = { backgroundColor: 'red' };

<button style={{ ...base, ...red }}>Hello</button>

Conditionally applying styles

Specifically used for toggling, true/false: show/hide etc.

  1. Ternary operator

<button className={isActive ? 'btn active' : 'btn'}>Click</button>

  1. Logical && (for adding a class)

<button className={`btn ${isActive && 'active'}`}>Click</button>

(Be careful: false can be printed; prefer conditional expression that results in empty string when false)

  1. Template literals with expression

<button className={`btn ${isActive ? 'active' : ''} ${size === 'lg' ? 'large' : ''}`}>Click</button>

Styled-components library

Install

npm install styled-components

// Home.tsx
import styled from "styled-components";

interface ButtonProps {
  primary?: boolean;
}

const Button = styled.button<ButtonProps>`
  background-color: ${(props) => (props.primary ? "blue" : "gray")};
  color: white;
  padding: 0.5rem 1rem;
  border-radius: 6px;

  &:hover {
    background-color: ${(props) => (props.primary ? "darkblue" : "darkgray")};
  }
`;

export default function Home() {
  return <Button primary>Primary Button</Button>;
}

classnames / clsx library — what problem they solve

As conditional class logic grows, string concatenation becomes messy. classnames (or clsx) makes this neat.

Install

npm install classnames
# or
npm install clsx

//Home.tsx
import classNames from 'classnames';

interface HomeProps {
  primary?: boolean;
  disabled?: boolean;
  size?: 'small' | 'medium' | 'large';
}


export default function Home({ primary = true, disabled = false, size = 'medium' }: HomeProps) {
  const classes = classNames('btn', {
    'btn--primary': primary,
    'btn--disabled': disabled
  }, `btn--${size}`);

  return <button className={classes}>Click</button>;
}

If I call Home component with these props:

<Home primary={false} disabled={false} size="small" />

I get this dynamic css:

false and small

Now let's update props and call it again

<Home primary={true} disabled={true} size="large" />

and see how style is updated:

true large

React Styling Methods

Best practices

My 2 Cents

Hold on to your glasses - article on CSS custom properties is coming up