CSS Modules Composition: Reuse and Global Styles
CSS Modules composition lets you reuse and combine styles across files, avoiding duplication and keeping your CSS maintainable. The composes keyword inherits styles from other classes, and the classnames library makes applying multiple classes in React clean and readable. You'll also learn to escape local scope with :global when needed for third-party components or global styling.
Key Takeaways
composeskeyword inherits all styles from another class, promoting DRY principles (Don't Repeat Yourself)- Composition works within the same file or across different
.module.cssfiles - Use the
classnameslibrary for conditionally applying multiple classes in React components - The
:global()syntax lets you create a global CSS class from within a CSS Module without hashing - Composition is essential for building scalable, maintainable styling systems in large React applications
What Is CSS Modules Composition?
Composition in CSS Modules is a feature that allows one class to inherit all the styles from another class. This is CSS Modules' answer to the DRY (Don't Repeat Yourself) principle. Instead of duplicating style declarations across multiple classes, you use the composes keyword to reference an existing class and inherit its properties. This is particularly useful when you have a base component style and need to create multiple variations (e.g., a base button with primary, secondary, and danger variants).
How Do You Compose Styles Within the Same File?
Creating Base and Variant Classes
When you have a base component style with multiple variations, composition eliminates duplication. Define a base class with common properties, then create variants that compose the base and add their own specific styles.
Button.module.css
.btn {
padding: 10px 15px;
border: none;
border-radius: 5px;
font-size: 1rem;
}
.primary {
composes: btn;
background-color: blue;
color: white;
}
.secondary {
composes: btn;
background-color: grey;
color: white;
}
.danger {
composes: btn;
background-color: red;
color: white;
}
When you apply className={styles.primary} in React, the rendered HTML element receives both the generated hash for .btn and the generated hash for .primary. This merges the styles, with properties in .primary overriding any conflicting properties from .btn. In practice, a browser inspector shows the button styled with padding, border-radius, and font-size from the base .btn class, plus the blue background from .primary.
Button.jsx
import React from 'react';
import styles from './Button.module.css';
function Button() {
return (
<div>
<button className={styles.primary}>Primary</button>
<button className={styles.secondary}>Secondary</button>
<button className={styles.danger}>Danger</button>
</div>
);
}
export default Button;
Why Composition Matters
Without composition, you would duplicate the .btn styles in every variant class. As the base styles grow (adding focus states, transitions, media queries), maintaining those duplicates becomes error-prone. Composition centralizes the base styles in one place, making updates simple and consistent.
How Do You Compose Styles from Other Files?
Building a Shared Utility Library
For larger projects, you often want to reuse styles across many components. Creating a shared utility CSS Module and composing from it is the cleanest approach.
utils.module.css — A shared library of utility classes
.flexCenter {
display: flex;
justify-content: center;
align-items: center;
}
.flexBetween {
display: flex;
justify-content: space-between;
align-items: center;
}
.shadow {
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
Card.module.css — Composes utilities for a Card component
.card {
composes: flexCenter from './utils.module.css';
padding: 20px;
background-color: white;
}
.cardWithShadow {
composes: shadow from './utils.module.css';
padding: 20px;
background-color: white;
}
Card.jsx
import React from 'react';
import styles from './Card.module.css';
function Card({ children, hasShadow }) {
const className = hasShadow ? styles.cardWithShadow : styles.card;
return <div className={className}>{children}</div>;
}
export default Card;
Now the card class automatically inherits the flexbox layout from utils.module.css. If you later update .flexCenter in utils.module.css, every component composing it benefits immediately. This pattern scales well for teams and large codebases where consistency is critical.
How Do You Apply Multiple Classes Dynamically in React?
The classnames Library Pattern
In React, you often need to apply classes conditionally (e.g., apply .primary if isPrimary is true, or .active if isActive is true). You can use template literals, but the standard, industry-recommended approach is the classnames library (also called clsx).
First, install it:
npm install classnames
Button.jsx — Using classnames
import React from 'react';
import styles from './Button.module.css';
import cn from 'classnames';
function Button({ isPrimary, isActive, isDisabled }) {
const classNames = cn(
styles.btn,
{
[styles.primary]: isPrimary,
[styles.active]: isActive,
[styles.disabled]: isDisabled,
}
);
return <button className={classNames}>Click Me</button>;
}
export default Button;
The cn() function takes multiple arguments. String arguments are always applied; object arguments apply the key (as a class name) only if the value is true. This approach is cleaner than template literals and more readable than ternary operators. It also handles falsy values gracefully (ignoring null, undefined, and false), preventing accidental "false" class names in your HTML.
Why Avoid Manual String Concatenation
Without classnames, you might write:
const className = `${styles.btn} ${isPrimary ? styles.primary : ''} ${isActive ? styles.active : ''}`;
This works but becomes hard to read with many conditions and leaves empty strings in the class list. The classnames library is the professional standard for this reason.
How Do You Create Global Styles Within a CSS Module?
Using the :global Syntax
Sometimes you need to define a truly global CSS class from within a CSS Module (e.g., for styling a third-party component library, or for global typography). The :global() syntax tells CSS Modules to not hash a specific class or selector; it passes it through to the global stylesheet unchanged.
globals.module.css
.myLocalClass {
color: blue;
}
:global(.my-global-class) {
padding: 20px;
font-weight: bold;
}
:global(body) {
background-color: #f0f0f0;
font-family: Arial, sans-serif;
}
:global(.some-third-party-lib) {
margin: 0 !important;
}
When you import this module, only myLocalClass appears as a property on the styles object. The :global declarations are injected directly into the document as global CSS, without hashing. This means any element with the class .my-global-class (set via a third-party library or manually) will receive those styles.
App.jsx — Using global and local classes
import React from 'react';
import styles from './globals.module.css';
function App() {
return (
<div>
<h1 className={styles.myLocalClass}>Local Style</h1>
{/* This div gets the global .my-global-class style (no styles.xxx) */}
<div className="my-global-class">Global Style</div>
{/* Third-party library elements also get the global style */}
</div>
);
}
export default App;
When to Use :global
Use :global() sparingly, only when you genuinely need a global style. Common cases include:
- Third-party component libraries — You need to override or harmonize styles for external UI libraries (e.g.,
react-modal,react-select) - Global typography — Base
body,h1,h2styles - Reset or normalize styles — Global browser resets
- Layout wrappers — Styles for the
htmlorbodythat must be global
For component-specific styles, always prefer local composition.
Frequently Asked Questions
Can I compose from multiple classes at once?
Yes. You can list multiple classes in a single composes rule separated by commas, or use multiple composes lines. For example:
.card {
composes: flexCenter, shadow from './utils.module.css';
padding: 20px;
}
What happens if a composed class and a local class have the same property?
The local class wins. CSS specificity rules apply—properties defined in the current class override properties from composed classes. If both .btn and .primary define color, the color from .primary takes precedence.
Do I need to install classnames, or can I use CSS-in-JS instead?
You don't need classnames if you use CSS-in-JS libraries like styled-components or Emotion, which handle dynamic styles natively. However, if you are using CSS Modules, classnames is the standard solution. For most React projects starting with CSS Modules, classnames is simpler and lighter than CSS-in-JS.
Can I compose from a node_modules package CSS file?
No. CSS Modules composition only works with local files. If you want to reuse styles from a third-party package, you need to import the package's CSS globally or use :global() to style their classes.
Is :global performance-intensive?
No. :global() has no performance penalty. It simply tells the CSS Modules compiler to not hash the selector. The browser treats global styles exactly like any other CSS rule.
Further Reading
Last updated: June 2, 2026 by Dr. Alex Turner