What it is:
- CSS files where class names and animations are scoped locally by default to the component.
- Avoids naming conflicts without needing unique class name conventions like BEM.
- Compiles class names into unique hashes at build time.
How it works in React:
import styles from './Button.module.css';
export default function Button() {
return <button className={styles.primary}>Click</button>;
}Pros:
- No runtime overhead — just plain CSS after build.
- Easy migration from traditional CSS.
- Works with preprocessors like Sass.
Cons:
- No dynamic styling based on props (you must toggle classes manually).
- No built-in theming.
What it is:
- CSS preprocessor adding features like variables, nesting, mixins, and partials.
- Outputs plain CSS at build time.
How it works in React (with CSS Modules for scoping):
/* Button.module.scss */
$primary-color: #4cafef;
.primary {
background: $primary-color;
&:hover {
background: darken($primary-color, 10%);
}
}Pros:
- Powerful syntax (nesting, variables, loops).
- Great for large-scale, structured styles.
Cons:
- Still static; no prop-based styles.
- Requires build setup.
What it is:
- A CSS-in-JS library that lets you write CSS directly in JavaScript files.
- Styles are scoped and can be dynamic based on props.
How it works in React:
import styled from 'styled-components';
const Button = styled.button`
background: ${(props) => (props.primary ? 'blue' : 'gray')};
color: white;
`;
export default function App() {
return <Button primary>Click</Button>;
}Pros:
- Dynamic styles using props.
- Automatic vendor prefixing.
- Scoped styles, easy theming.
- Conditional styling without extra class toggles.
Cons:
- Runtime cost (styles generated at runtime unless using Babel plugin).
- Can increase bundle size.
What it is:
- Another CSS-in-JS library, similar to Styled Components but more flexible.
- Supports both styled API and object styles.
Styled API:
/** @jsxImportSource @emotion/react */
import styled from '@emotion/styled';
const Button = styled.button`
background: hotpink;
`;Object Styles:
import { css } from '@emotion/react';
const buttonStyle = css({
background: 'hotpink',
padding: '10px',
});Pros:
- Two styling approaches (template literals or objects).
- Theming and prop-based styles supported.
- Smaller runtime than some CSS-in-JS solutions.
Cons:
- Still runtime cost.
- Needs setup for optimal performance.
What it is:
- Utility-first CSS framework with predefined classes.
- Encourages styling directly in the className instead of creating separate style files.
How it works in React:
export default function Button() {
return (
<button className="bg-blue-500 hover:bg-blue-700 text-white px-4 py-2 rounded">
Click
</button>
);
}Pros:
- No need to invent class names.
- Consistent design system out of the box.
- PurgeCSS removes unused classes, keeping bundles small.
- Very fast for prototyping.
Cons:
- JSX can look messy with long class lists.
- Learning curve for class naming.
- Custom styles need configuration in tailwind.config.js.
| Feature | CSS Modules | SCSS/Sass | Styled Components | Emotion | Tailwind CSS |
|---|---|---|---|---|---|
| Scoping | Yes | No (unless with modules) | Yes | Yes | Yes (utility) |
| Dynamic styles | No | No | Yes | Yes | Limited (conditional class names) |
| Build-time only | Yes | Yes | No (runtime) | No (runtime) | Yes |
| Theming support | No | No | Yes | Yes | Yes (via config) |
| Ease of migration | High | Medium | Medium | Medium | Medium/Low |
| Performance | High | High | Medium | Medium | High |