En el artículo anterior hemos visto que uno de los desafíos más comunes es gestionar el ámbito global de los estilos sin caer en conflictos de nombres.
Es decir, si llamáis a una clase .tarjeta en un sitio, se aplica en todos lados. Esto obliga a usar nombres kilométricos como .Sidebar__UserProfile--active para evitar colisiones.
Aquí es donde CSS Modules entra en juego, una técnica que permite encapsular estilos a nivel de componente, evitando que los estilos de un componente afecten a otros.
¿Qué son los CSS Modules?
Un CSS Module no es una librería que debáis instalar, ni una sintaxis nueva y extraña. Es simplemente un archivo CSS normal donde todas las clases tienen ámbito local por defecto.
Si el archivo termina en .module.css, Vite lo tratará como un módulo.
¿Por qué funciona esto? Gracias a una convención de nombres y al proceso de compilación de herramientas como Vite.
Implementación paso a paso
Para ver su uso en un ejemplo, vamos a refactorizar nuestro componente Boton para que use módulos.
src/
├── components/
│ ├── Button/
│ │ ├── Button.jsx
│ │ ├── Button.module.css
La convención común es nombrar los archivos de estilos con la extensión .module.css.
Creamos el archivo Boton.module.css. Fijaos en la extensión. Dentro, escribimos CSS estándar, pero ahora podemos usar nombres genéricos sin miedo.
/* Boton.module.css */
.boton {
background-color: #007bff;
color: white;
padding: 10px 20px;
border-radius: 5px;
}
.error {
background-color: red;
}
Aquí está la mayor diferencia. Ya no importamos el archivo “al aire” (import './file.css'). Ahora lo importamos como un objeto, llamado normalmente styles.
// Boton.jsx
import styles from './Boton.module.css'; // Importamos el objeto
export default function Boton() {
return (
// Accedemos a las clases como propiedades del objeto
<button className={styles.boton}>
Click me
</button>
);
}
- Cero conflictos: Podéis dejar de preocuparos por si
.containerya existe en otro sitio. - Mantenimiento: Si borráis el componente
Boton.jsx, podéis borrarBoton.module.csscon la seguridad de que no estabais rompiendo estilos en otra parte de la web. - Código explícito: Al leer
styles.titulo, sabéis exactamente de dónde viene ese estilo.
- Sintaxis: Hay que escribir
styles.delante de todo. - Menos reutilización: No podéis confiar en clases globales de utilidad a menos que las importéis explícitamente.
CamelCase vs Kebab-case
En CSS solemos usar guiones (.btn-primary). Sin embargo, en JavaScript, los guiones no son válidos para acceder a propiedades con punto (styles.btn-primary ❌).
Si usáis guiones en vuestro CSS Module, tendréis que usar la notación de corchetes:
<button className={styles['btn-primary']}>
Esto es un poco incómodo. Por eso, cuando trabajamos con CSS Modules, es una buena práctica escribir las clases en camelCase:
/* Preferible en Modules */
.btnPrimary { ... }
/* Mucho más limpio */
<button className={styles.btnPrimary}>
Combinando clases (Composition)
¿Qué pasa si queremos aplicar varias clases? Como styles.clase devuelve un string, podemos usar las mismas técnicas que vimos en el artículo anterior.
Es decir, así funcionaría usando Template Literals.
<div className={`${styles.tarjeta} ${styles.tarjetaActiva}`}>
Y así con un array
<div className={[styles.tarjeta, styles.tarjetaActiva].join(' ')}>
Excepciones globales
A veces, incluso dentro de un módulo, necesitamos definir una clase global. Por ejemplo, para sobrescribir un estilo de una librería de terceros que inserta HTML fuera de nuestro control.
Para “escapar” del ámbito local, usamos la pseudo-clase :global.
/* MiComponente.module.css */
.local {
color: red; /* Se transformará en ._local_hash */
}
:global(.libreria-externa) {
color: blue; /* Se mantendrá tal cual: .libreria-externa */
}
