Vite.js Built‑In CSS Modules: Enable, Use, and Avoid Common Pitfalls
Learn how Vite automatically treats files ending in .module.css (and variants) as CSS Modules, how to import them, tweak the hash format, and what to watch out for when mixing global and module styles.
26 Sept 2026, 02:25 UTC

Why Vite Automatically Scopes CSS Modules
Vite’s default build pipeline treats any file ending in .module.css, .module.scss, .module.sass, .module.less, or .module.styl as a CSS Module. The tool generates a unique hash for each class name at build time, preventing global namespace collisions. This behaviour is enabled out‑of‑the‑box – no extra plugins or configuration required.
Importing a Module in a Component
With a module file in place, you can import it like a normal JavaScript module. The imported object maps local class names to hashed identifiers. Below is a minimal React example.
// src/components/Button.module.css
.primary {
background: #0070f3;
color: white;
}
// src/components/Button.jsx
import styles from './Button.module.css';
export default function Button({ children }) {
return {children};
}
Running npm run dev will render a button with the hashed class name .primary-3a9b1 (hash will differ). The CSS is injected via Vite’s HMR, so editing the module file updates the button style instantly.
Customizing the Hash Format
If you need a predictable or shorter hash, adjust the vite.config.js css.modules options. The two most common tweaks are generateScopedName and localsConvention.
// vite.config.js
import { defineConfig } from 'vite';
export default defineConfig({
css: {
modules: {
// Keep the original class name and append a short hash
generateScopedName: '[name]__[local]___[hash:base64:5]',
// Allow camelCase access for BEM‑style classes
localsConvention: 'camelCaseOnly',
},
},
});
With the above config, a class .primaryButton becomes Button__primaryButton___3f9a4 and can be accessed via styles.primaryButton in JavaScript.
Typical Use Cases
- Component‑Scoped Styling: Keep styles local to a component, avoiding leakage into the global stylesheet.
- Theming: Dynamically import different module files based on a theme flag and let Vite hash them separately.
- SSR & Hydration: In frameworks like Nuxt or SvelteKit, the hashed names are consistent between server and client, preventing style flicker.
Common Pitfalls and How to Avoid Them
- Mixing Global and Module Files in One Component
Vite treats.cssand.module.cssdifferently. Importing a global file inside a component that also uses a module file can lead to duplicate class names or missing styles. Keep global styles in a dedicated file (e.g.,global.css) and import them once inmain.jsxorApp.vue. - String Interpolation Bypassing the Module Map
Using template literals like${styles.primary}inside aclassNamestring that isn’t processed by the bundler will output the raw string, not the hashed value. Stick to dot notation orclassNames(styles.primary, otherClass). - Expecting Hashes to Persist Across Builds with Content Changes
The hash is derived from the file content. Minor edits change the hash, which is expected. If you need stable names across builds, use thegenerateScopedNameoption to embed the original name. - HMR Not Updating on CSS Module Changes
Vite’s HMR updates only when the file content changes. If you modify a property but the hash remains the same, the browser may not apply the new style. Force a reload or clear the cache if you suspect stale styles.
Verification Checklist
- Create a new Vite project:
npm create vite@latest myapp -- --template react. - Add
Button.module.csswith a simple rule. - Import it in a React component and run
npm run dev. - Open
http://localhost:5173, confirm the button renders with the expected style. - Change the CSS background color, observe the update without a full reload.
- Run
npm run buildand inspectdist/assets/index.[hash].cssto confirm the class name is hashed.
Limitations and Edge Cases
Vite’s CSS Modules support is designed for simple component‑scoped styling. It does not support:
- Nested
@keyframesor@font-facedeclarations that reference class names. - Dynamic class names generated at runtime outside the module map.
- Server‑side rendering of modules that rely on
documentduring the build step.
For advanced use cases, consider using a dedicated CSS‑in‑JS library or a full CSS preprocessor setup.
Conclusion
Vite’s built‑in CSS Modules feature offers a zero‑config, fast, and reliable way to scope styles locally. By following the import pattern, customizing the hash format when needed, and avoiding common mistakes, you can keep your component styles clean and maintainable across development, HMR, and production builds.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.