CSS Modules are one of the most popular ways to style in React. They work like a system of isolated control panels on a spaceship -- each module has its own controls that don't interfere with others.
CSS Modules are regular CSS files with the
.module.css extension. The difference is that all class names are automatically unique -- React generates unique identifiers, so styles from one component never override styles of another.1/* SpacePanel.module.css */
2.panel {
3 background: #0d1137;
4 border: 2px solid #3949ab;
5 border-radius: 12px;
6 padding: 20px;
7}
8
9.title {
10 color: #7c4dff;
11 font-size: 24px;
12 margin-bottom: 16px;
13}
14
15.statusActive {
16 color: #00e676;
17 font-weight: bold;
18}We import a CSS Module as an object and reference classes like object properties:
1import styles from './SpacePanel.module.css';
2
3function SpacePanel({ title, status }) {
4 return (
5 <div className={styles.panel}>
6 <h2 className={styles.title}>{title}</h2>
7 <span className={styles.statusActive}>
8 Status: {status}
9 </span>
10 </div>
11 );
12}In the generated HTML, the
panel class becomes something like SpacePanel_panel_x7k2f -- completely unique!We often need to combine multiple CSS classes. In CSS Modules, we can do this in several ways:
1// Template literal
2<div className={`${styles.panel} ${styles.dark}`}>
3
4// With condition
5<div className={`${styles.panel} ${isActive ? styles.active : ''}`}>
6
7// Array with join
8<div className={[styles.panel, styles.dark].join(' ')}>CSS Modules offer a special
composes directive that allows inheriting styles from other classes:1/* base.module.css */
2.baseButton {
3 padding: 10px 20px;
4 border: none;
5 border-radius: 6px;
6 cursor: pointer;
7 font-size: 14px;
8 transition: all 0.3s ease;
9}
10
11/* SpaceButton.module.css */
12.primaryButton {
13 composes: baseButton from './base.module.css';
14 background: #7c4dff;
15 color: white;
16}
17
18.dangerButton {
19 composes: baseButton from './base.module.css';
20 background: #f44336;
21 color: white;
22}Thanks to
composes, we avoid duplicating styles while maintaining isolation.If you need a global class (e.g., for external libraries), use
:global:1/* Layout.module.css */
2:global(.container) {
3 max-width: 1200px;
4 margin: 0 auto;
5}
6
7.localClass {
8 /* This is local */
9 color: #64b5f6;
10}We often need to change styles based on props or state. Here are some patterns:
1import styles from './Alert.module.css';
2
3function Alert({ type = 'info', message }) {
4 // Dynamically selecting a class based on the prop
5 const alertClass = styles[type]; // styles.info, styles.warning, styles.error
6
7 return (
8 <div className={`${styles.alert} ${alertClass}`}>
9 <p className={styles.message}>{message}</p>
10 </div>
11 );
12}1/* Alert.module.css */
2.alert {
3 padding: 16px;
4 border-radius: 8px;
5 border-left: 4px solid;
6}
7
8.info {
9 background: #1a237e;
10 border-color: #64b5f6;
11 color: #90caf9;
12}
13
14.warning {
15 background: #4a3800;
16 border-color: #ffa726;
17 color: #ffcc80;
18}
19
20.error {
21 background: #4a0000;
22 border-color: #f44336;
23 color: #ef9a9a;
24}Notice that
styles[type] lets you dynamically select a class based on a variable -- it's like switching modes on a control panel.For more complex class combinations, it's worth using the
clsx or classnames library:1import clsx from 'clsx';
2import styles from './Button.module.css';
3
4function SpaceButton({ variant, size, disabled }) {
5 return (
6 <button className={clsx(
7 styles.button,
8 styles[variant], // styles.primary or styles.secondary
9 styles[size], // styles.small or styles.large
10 { [styles.disabled]: disabled } // conditionally
11 )}>
12 Engage!
13 </button>
14 );
15}typed-css-modules)CSS Modules are a solid, proven choice for styling React components. They're like reliable control panels on a spaceship -- simple, predictable, and conflict-free.