Parcel 10 ๐ฆ CSS Modules โ Scoped styles out of the box
A CSS Module is a CSS file whose class names are scoped to the module that imports it. The file is written like ordinary CSS, but the class names are rewritten at build time into unique identifiers. The importing JavaScript file receives an object that maps the original names to the generated ones. Two components can both define a .button class, and the two rules will not collide because the generated names include a hash of the file path.
Parcel enables CSS Modules by the filename convention: a file ending in .module.css is a module, and a file ending in .css is global. No configuration is needed for the default behavior. The scoping, the hash, and the mapping object are all produced by the built-in CSS transformer .
This chapter covers the .module.css convention, the import and usage pattern, the default generated class name format, the global escape hatch for a rule that must not be scoped, and the configuration options for customizing the naming pattern and enabling modules globally.
Key point: The default class name is _<local>_<hash> in development and <name>_<local>_<hash> in production. The hash is derived from the file path, so two files with the same class name never collide. The .module.css suffix is what triggers the scoping.
Why CSS Modules exist
The global scope problem. Ordinary CSS is global. A .button rule in header.css applies to every element with class="button" on the page, including the button in footer.css that was supposed to be red. The larger the application, the more likely a class name collision becomes, and the harder it is to trace which file is responsible for a style.
The naming convention problem. Teams invent prefixes (.header-button, .footer-button) or use BEM (.button--primary) to avoid collisions. These conventions work, but they depend on every developer following them consistently. A CSS Module enforces the scoping mechanically: the class name is unique because the build rewrites it, not because the developer remembered to prefix it.
The dead code problem. A global stylesheet accumulates rules that are no longer used. Removing a rule requires checking every file that might reference the class. A CSS Module is tied to the file that imports it. When the importing file is removed, the module has no importers, and the bundler can drop it.
a. The .module.css convention
The suffix is the entire configuration for the default behavior. A file named button.module.css is a module. A file named button.css is global.
/* button.module.css */
.button {
background: blue;
padding: 8px 16px;
border-radius: 4px;
}
The file is written like any other CSS. The class names are local by default; they will be scoped when the file is imported.
The generated class name follows the pattern _<local>_<hash> in development and <name>_<local>_<hash> in production, where <name> is the file name without the extension and <hash> is a short hash of the file path. The development format is shorter for readability in the browser’s developer tools; the production format includes the file name for debugging .
b. Importing and using a module
The default import of a module returns an object. The object’s keys are the original class names, and the values are the generated ones.
import styles from './button.module.css'
element.className = styles.button
At runtime, styles.button is something like _button_a1b2c3 in development or button_button_a1b2c3 in production. The CSS file contains the corresponding rule.
The import works from JavaScript and TypeScript. Parcel’s transformer generates the .d.ts type declaration for the module automatically, so TypeScript knows the shape of the styles object without a manual declaration file .
A module can also be imported for its side effects, but the class mapping is only available if the default import is used. A bare import './button.module.css' includes the styles but does not expose the class names, which defeats the purpose of using a module.
c. The global escape hatch and configuration
A rule inside a module can be marked as global with the :global pseudo-class. The rule is not scoped, and its class name is preserved.
/* button.module.css */
:global(.external-widget) {
margin: 0;
}
This is useful when a module needs to style an element from a third-party library whose class name it does not control. The :global selector applies to the class inside the parentheses, and the rest of the module remains scoped.
The naming pattern and the global mode are configurable through the @parcel/transformer-css key in package.json.
{
"@parcel/transformer-css": {
"modules": {
"pattern": "[name]-[local]-[hash]",
"global": false
}
}
}
The pattern field controls the generated class name format. The [name], [local], and [hash] placeholders correspond to the file name, the original class name, and the content hash. The global field, when set to true, treats every CSS file as a module, not just the ones with the .module.css suffix .
Complete Example Session
/* ============================================
PART 1: THE MODULE FILE
============================================ */
/* button.module.css */
.button {
background: blue;
padding: 8px 16px;
}
.primary {
background: green;
}
// ============================================
// PART 2: THE IMPORT
// ============================================
import styles from './button.module.css'
console.log(styles)
// { button: '_button_a1b2c3', primary: '_primary_d4e5f6' }
// ============================================
// PART 3: THE USAGE
// ============================================
element.className = styles.button
// The element's class is '_button_a1b2c3'
/* ============================================
PART 4: THE OUTPUT CSS
============================================ */
._button_a1b2c3 {
background: blue;
padding: 8px 16px;
}
._primary_d4e5f6 {
background: green;
}
/* ============================================
PART 5: A SECOND MODULE WITH THE SAME CLASS
============================================ */
/* card.module.css */
.button {
background: red;
}
// ============================================
// PART 6: NO COLLISION
// ============================================
import buttonStyles from './button.module.css'
import cardStyles from './card.module.css'
buttonStyles.button // '_button_a1b2c3'
cardStyles.button // '_button_g7h8i9'
// The two classes are different.
// Both rules are applied to their respective elements.
/* ============================================
PART 7: THE GLOBAL ESCAPE HATCH
============================================ */
/* button.module.css */
:global(.external-widget) {
margin: 0;
}
.button {
background: blue;
}
/* ============================================
PART 8: THE GLOBAL OUTPUT
============================================ */
.external-widget {
margin: 0;
}
._button_a1b2c3 {
background: blue;
}
/* The global selector is not scoped. */
// ============================================
// PART 9: THE CONFIGURATION
// ============================================
{
"@parcel/transformer-css": {
"modules": {
"pattern": "[name]-[local]",
"global": false
}
}
}
// ============================================
// PART 10: THE GLOBAL MODE
// ============================================
{
"@parcel/transformer-css": {
"modules": {
"global": true
}
}
}
// Every CSS file is treated as a module.
// The .module.css suffix is no longer required.
The ten parts covered the module file, the import, the usage, the output CSS, a second module with the same class, the collision avoidance, the :global escape hatch, the global output, the configuration, and the global mode.
Quick Reference
The convention
| Filename | Behavior |
|---|---|
button.module.css | CSS Module, scoped |
button.css | Global CSS |
Import and usage
| Item | Example |
|---|---|
| Import | import styles from './button.module.css' |
| Class map | styles.button |
| Generated name (dev) | _button_a1b2c3 |
| Generated name (prod) | button_button_a1b2c3 |
Selectors
| Selector | Behavior |
|---|---|
.button | Scoped |
:global(.external) | Not scoped |
:local(.button) | Explicitly scoped |
Configuration
| Option | Effect |
|---|---|
modules.pattern | Custom class name format |
modules.global | Treat all CSS as modules |
[name] | File name placeholder |
[local] | Original class name |
[hash] | Content hash |
Best Practices
โ Do This:
/* Use the .module.css suffix */
/* button.module.css */ // โ
// Use the default import to get the class map
import styles from './button.module.css' // โ
// Reference the class through the map
element.className = styles.button // โ
/* Use :global for third-party class names */
:global(.external-widget) { margin: 0; } // โ
โ Don’t Do This:
// Import a module for its side effects only
import './button.module.css' // styles not available // โ
/* Use a class name without referencing the map */
/* button.module.css */
.button { } // the DOM needs styles.button // โ
// Hardcode the generated class name
element.className = '_button_a1b2c3' // โ
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
styles.button is undefined | Filename missing .module | Rename to button.module.css |
| Class name not applied | The class is used directly, not through the map | Use styles.button |
| Global class not scoped | Missing :global | Wrap the selector in :global() |
| TypeScript error on import | No type declaration | Parcel generates the .d.ts automatically |
| Collision still occurs | The file is a .css, not a .module.css | Use the suffix |
Real-World Examples
1. Module file
/* card.module.css */
.card { padding: 16px; }
2. Import and use
import styles from './card.module.css'
element.className = styles.card
3. Two classes
element.className = `${styles.card} ${styles.elevated}`
4. :global selector
:global(.leaflet-container) { z-index: 0; }
5. :local selector
:local(.button) { background: blue; }
6. Custom pattern
{ "modules": { "pattern": "[name]-[local]" } }
7. Global mode
{ "modules": { "global": true } }
8. Multiple classes from one module
import { card, elevated } from './card.module.css'
9. Development class name
_button_a1b2c3
10. Production class name
button_button_a1b2c3
Visual
The scoping mechanism
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ button.module.css โ
โ .button { background: blue; } โ
โ โ
โ Build โ
โ โ โ
โ โผ โ
โ Output CSS โ
โ ._button_a1b2c3 { background: blue; } โ
โ โ
โ Import map โ
โ { button: '_button_a1b2c3' } โ
โ โ
โ The class name is unique to this file. โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
No collision
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ button.module.css โ
โ .button โ _button_a1b2c3 โ
โ โ
โ card.module.css โ
โ .button โ _button_d4e5f6 โ
โ โ
โ The two rules do not override each other. โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
The class name format
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ DEVELOPMENT PRODUCTION โ
โ โ
โ _button_a1b2c3 button_button_a1b2c3 โ
โ โ
โ Shorter, readable Includes file name for debugging โ
โ โ
โ Pattern: [name]-[local]-[hash] can override both. โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
The global escape hatch
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ button.module.css โ
โ โ
โ .button { } โ scoped โ
โ :global(.external) { } โ not scoped โ
โ โ
โ The :global() wrapper exempts the selector. โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Summary
| Item | Value |
|---|---|
| Convention | *.module.css |
| Import | import styles from './file.module.css' |
| Class map | styles.className |
| Generated name (dev) | _<local>_<hash> |
| Generated name (prod) | <name>_<local>_<hash> |
| Global selector | :global(.class) |
| Local selector | :local(.class) |
| Configuration key | @parcel/transformer-css |
| Global mode | modules.global: true |
Key takeaways:
- The
.module.csssuffix is the entire configuration for the default behavior. A file with the suffix is a module; a file without it is global. No config file is needed for the scoping to work. - The default import returns a class map. The keys are the original class names, and the values are the generated ones. The element must use
styles.className, not the original name. - The generated class name includes a file-path hash. Two modules with the same class name produce different generated names, so the rules cannot collide.
:globalis the escape hatch. A rule that must apply to an element from a third-party library can be marked as global. The rest of the module remains scoped.- The pattern and the global mode are configurable. The
patternfield controls the class name format, andglobal: truetreats every CSS file as a module, not just the ones with the suffix. - TypeScript declarations are generated automatically. Parcel produces a
.d.tsfor the module, so the class map is typed without a manual declaration file.
Remember: A CSS Module is a CSS file whose class names are scoped to the module that imports it. The .module.css suffix enables the scoping, the default import exposes the class map, and the generated names prevent collisions. Use the map, not the original class name. Use :global for third-party selectors. The result is the same CSS syntax with the collision problem solved mechanically rather than by convention.
Stop using slow, ad-bloated tool sites! ๐คฎ
๐ Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
โ
Finance (Mortgage, Interest, Inflation)
โ
Tech (Base64, JSON, Dev Suite, IP)
โ
Health (BMI, BMR, TDEE)
โ
Productivity (Timer, Workspace, QR)
โก๏ธ Fast & Private
๐ No data leaves your device
๐ 100% Free
๐ Use it now: https://tools.kandz.me
๐ Bookmark itโyouโll need it later!