Where styles live in React?
Global CSS files: loaded once (e.g., index.css
) and apply rules globally.
CSS Modules:.module.css
files that are locally scoped (unique class names at build time).
CSS-in-JS libraries: e.g., styled-components
, which generate CSS from JS and scope to components.
Inline styles (style objects): React style={{ ... }}
prop.
Utility-first libraries: e.g., Tailwind (class-based).
Other: CSS-in-CSS preprocessors (Sass/LESS), Shadow DOM (web components), etc.
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
Keep a single (or a few) CSS files for the entire app, often in src/styles/
.
Usage: import './styles/index.css'
in index.tsx
or App.tsx
.
Can accidentally create naming collisions; not scoped.
src/
ββ styles/
β ββ index.css <-- resets, typography
β ββ variables.css <-- CSS variables
β ββ theme.css
3. CSS-in-JS / Styled-components
Fully scoped, dynamic styles, supports theming.
Runtime cost, dependency on a library.
const Container = styled.div`
padding: 20px;
background: lightblue;
`;
4. Hybrid approach
Combine approaches:
Colocate CSS Modules for component-specific styles.
Global styles/
folder for resets, variables, themes.
src/
ββ components/
β ββ Home/
β β ββ Home.tsx
β β ββ Home.module.css
ββ styles/
β ββ index.css
β ββ variables.css
React usesclassName
instead of class
but why?
React uses JSX and TSX, which looks like HTML but is actually JavaScript under the hood.
In JavaScript and TypeScript, class
is a reserved keyword (used for ES6 classes).
Using class
in JSX ot TSX would conflict with the JS/ TS syntax.
React maps className
in JSX/TSX to the class
attribute in the rendered HTML.
//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
).
Rules cascade globally.
Easy to use for resets, typography, layout, theme variables.
Can lead to naming collisions and unintended overrides unless careful (BEM, prefixing).
/* 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:
CSS Modules: class names become unique at build-time.
Styled-components / Emotion: components get generated unique class names; styles colocated with components.
Inline styles: inherently scoped to an element.
/* 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.
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>
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.
Ternary operator
<button className={isActive ? 'btn active' : 'btn'}>Click</button>
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)
Template literals with expression
<button className={`btn ${isActive ? 'active' : ''} ${size === 'lg' ? 'large' : ''}`}>Click</button>
Styled-components library
The styles live next to your component code.
You can use TypeScript/ JavaScript variables and props in your styles.
It automatically creates unique class names so the styles donβt conflict with other components.
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
Global CSS: use quotes className="btn" =
styles from normal CSS, applies everywhere.
Scoped CSS: use {} className={styles.btn}
= unique to the component.
Inline Styles: use {{}}style={{ color: 'red' }}
= applies only to that element.
Styled-Components, defined in variable β styled.button\
...`` = co-located, dynamic, unique class names.
Combination β mix className
+ style
= override or combine multiple styles.
Best practices
Prefer scoped styles (CSS Modules / styled-components) for components.
Use global CSS for resets and app-level variables.
Avoid heavy use of inline styles; use them when values are dynamic and simple.
Use classnames
/clsx
to manage complex conditional class logic.
Use CSS variables for theming and runtime tweaks.
Keep styling colocated with component logic when it improves maintainability β but balance with team tooling and performance.
My 2 Cents
Where styles live: Global CSS, CSS Modules (scoped), CSS-in-JS (styled-components), inline styles, utility libraries (Tailwind).
Organizing CSS: Colocate component styles (Home.tsx + Home.module.css
), global styles folder, hybrid approach.
Global vs Scoped: Global CSS affects everything; scoped CSS (modules/styled-components) is local to the component.
Inline styles: Quick, dynamic, element-specific; limited features.
Multiple & conditional styles: Combine classes, use ternary/logical operators, or libraries like classnames
/clsx
.
Styled-components: Scoped, dynamic, prop-driven, generates unique class names automatically.
Best practice: Colocate component styles for maintainability, use global CSS for resets/themes, and use inline styles sparingly.
Hold on to your glasses - article on CSS custom properties is coming up