CSS Modules
Hasta ahora hemos usado CSS global, donde todas las clases están disponibles en toda la aplicación. En esta clase aprenderemos a usar CSS Modules, una técnica que nos permite escribir CSS con ámbito local para cada componente, evitando conflictos de nombres.
El problema con CSS global
Imagina que tienes estos estilos globales:
/* styles.css */
.button {
background-color: blue;
color: white;
}
/* otro-archivo.css */
.button {
background-color: red; /* ⚠️ Sobreescribe el anterior */
font-size: 20px;
}
Problemas:
- ❌ Colisiones de nombres - Dos clases
.buttonse pisan entre sí - ❌ Difícil de mantener - No sabes dónde se usa cada clase
- ❌ Miedo a romper algo - Cambiar un estilo puede afectar otros componentes
- ❌ No hay encapsulación - Los estilos de un componente afectan a otros
¿Qué son los CSS Modules?
Los CSS Modules son archivos CSS normales pero con una convención especial: el nombre del archivo termina en .module.css.
❌ Pagination.css → CSS global
✅ Pagination.module.css → CSS Module (ámbito local)
Característica principal: Las clases CSS tienen ámbito local automáticamente. React (Vite/Webpack) las transforma para que sean únicas.
Creando el CSS Module para Pagination
Vamos a crear estilos para nuestro componente Pagination.
1. Crear el archivo Pagination.module.css
Creamos el archivo junto al componente:
src/
components/
Pagination.jsx
Pagination.module.css ← Nuevo archivo
Convención de nombres:
- El archivo debe llamarse igual que el componente
- Debe tener la extensión
.module.css - Se coloca en la misma carpeta que el componente
2. Escribir los estilos
/* src/components/Pagination.module.css */
.pagination {
display: flex;
justify-content: center;
gap: 0.5rem;
margin-block: 2rem;
a {
display: flex;
align-items: center;
justify-content: center;
width: 2.5rem;
height: 2.5rem;
text-decoration: none;
color: var(--text-muted);
border-radius: 0.375rem;
transition: all 0.3s;
&:hover,
&:focus {
background-color: #fff;
}
&:active {
transform: scale(0.9);
}
}
}
.isActive {
background-color: var(--primary-light);
color: white;
pointer-events: none;
}
Notas sobre la sintaxis:
- Usamos anidación (
adentro de.pagination) - Esto funciona con herramientas modernas como Vite - Usamos
&:hoverpara pseudoclases - Usamos
margin-block(propiedad lógica de CSS moderno) - Usamos variables CSS (
var(--primary-light))
3. Importar y usar el CSS Module
Ahora importamos el CSS Module en nuestro componente:
// src/components/Pagination.jsx
import styles from './Pagination.module.css'
export function Pagination({ currentPage = 1, totalPages = 5, onPageChange }) {
// ...
return (
<nav className={styles.pagination}>
<a href="#" style={styleLinkLeft} onClick={handlePrevious}>
<!-- Icono de flecha izquierda -->
</a>
{pages.map((page) => (
<a
key={page}
className={currentPage === page ? styles.isActive : ''}
href="#"
onClick={(e) => handlePageClick(e, page)}
>
{page}
</a>
))}
<a href="#" style={styleLinkRight} onClick={handleNext}>
<!-- Icono de flecha derecha -->
</a>
</nav>
)
}
Puntos clave:
// 1. Importar el CSS Module
import styles from './Pagination.module.css'
// 2. Usar las clases con styles.nombreClase
<nav className={styles.pagination}>
{/* ... */}
</nav>
// 3. Aplicar clases condicionales
<a className={currentPage === page ? styles.isActive : ''}>
{page}
</a>
¿Cómo funcionan los CSS Modules?
Cuando importas un CSS Module, React (Vite/Webpack) transforma los nombres de las clases para hacerlos únicos:
Código que escribes
import styles from './Pagination.module.css'
;<nav className={styles.pagination}>
<a className={styles.isActive}>1</a>
</nav>
Lo que se importa
styles = {
pagination: 'Pagination_pagination__a1b2c',
isActive: 'Pagination_isActive__d3e4f',
}
HTML generado en el navegador
<nav class="Pagination_pagination__a1b2c">
<a class="Pagination_isActive__d3e4f">1</a>
</nav>
Resultado: Los nombres de clase son únicos y no colisionan con otros componentes.
Formato del nombre generado
El nombre generado sigue este patrón:
[Componente]_[clase-original]__[hash-único]
Ejemplos:
.pagination → Pagination_pagination__a1b2c
.isActive → Pagination_isActive__d3e4f
.button → Button_button__x7y8z
Comparación: CSS Global vs CSS Modules
CSS Global
/* Pagination.css */
.pagination {
display: flex;
}
.isActive {
background-color: blue;
}
// Pagination.jsx
import './Pagination.css'
function Pagination() {
return (
<nav className="pagination">
<a className="isActive">1</a>
</nav>
)
}
Problemas:
- ⚠️ Si otro componente usa
.isActive, pueden colisionar - ⚠️ Los estilos están disponibles globalmente
- ⚠️ No sabes qué componente usa cada clase
CSS Modules
/* Pagination.module.css */
.pagination {
display: flex;
}
.isActive {
background-color: blue;
}
// Pagination.jsx
import styles from './Pagination.module.css'
function Pagination() {
return (
<nav className={styles.pagination}>
<a className={styles.isActive}>1</a>
</nav>
)
}
Ventajas:
- ✅ No hay colisiones:
.isActivees único para este componente - ✅ Los estilos están encapsulados
- ✅ Autocompletado en el editor (puedes ver
styles.) - ✅ Error si usas una clase que no existe
Ventajas de los CSS Modules
1. ✅ Sin colisiones de nombres
Puedes usar el mismo nombre de clase en diferentes componentes:
/* Button.module.css */
.button {
background-color: blue;
}
/* Card.module.css */
.button {
background-color: red; /* ✅ No colisiona con Button */
}
2. ✅ Encapsulación y mantenibilidad
Los estilos están junto al componente:
src/
components/
Pagination/
Pagination.jsx
Pagination.module.css ← Estilos aquí
- Fácil de encontrar los estilos de un componente
- Si borras el componente, borras sus estilos
- Cambiar un estilo no afecta otros componentes
3. ✅ Autocompletado y detección de errores
Tu editor puede autocompletar las clases:
import styles from './Pagination.module.css'
// Escribes: styles.
// Tu editor sugiere: pagination, isActive
<nav className={styles.pagination}>
{/* Tu editor sabe que existe */}
</nav>
// ❌ Error de TypeScript si usas una clase que no existe
<nav className={styles.paginashon}>
{/* TypeScript te avisa que 'paginashon' no existe */}
</nav>
4. ✅ Eliminación automática de CSS no usado
Las herramientas de build pueden detectar qué clases se usan:
// Si nunca usas styles.isActive, puede ser eliminado del bundle
5. ✅ Convive con CSS global
Puedes mezclar CSS Modules con CSS global:
import styles from './Pagination.module.css'
import './global.css'
/*
<nav className={`${styles.pagination} container`}>
↑ CSS Module ↑ CSS global
</nav>
*/
Aplicando múltiples clases
Hay varias formas de aplicar múltiples clases CSS Module:
Opción 1: Template literals
<a className={`${styles.link} ${styles.active}`}>Link</a>
Opción 2: Condicionales
<a className={currentPage === page ? styles.isActive : ''}>{page}</a>
Opción 3: Combinar módulo y global
<nav className={`${styles.pagination} container`}>
{/* ↑ CSS Module ↑ CSS global */}
</nav>
Opción 4: Array + join (más legible)
<a className={[styles.link, isActive && styles.active].filter(Boolean).join(' ')}>Link</a>
Opción 5: Librería classnames
import classNames from 'classnames'
// <a className={classNames(styles.link, { [styles.active]: isActive })}>Link</a>
Cuándo usar CSS Modules vs CSS global
Usa CSS Modules para:
- ✅ Estilos de componentes específicos
- ✅ Componentes reutilizables (Button, Card, Modal)
- ✅ Cuando quieres evitar colisiones
- ✅ Proyectos medianos/grandes
Usa CSS global para:
- ✅ Reset CSS / Normalize
- ✅ Variables globales (
:root) - ✅ Estilos del
body,html - ✅ Utilidades genéricas (
.container,.text-center) - ✅ Fuentes y tipografía base
Errores comunes
❌ Olvidar .module.css
/* ❌ Pagination.css - NO es CSS Module */
.pagination {
}
/* ✅ Pagination.module.css - SÍ es CSS Module */
.pagination {
}
❌ Usar nombres con guiones
/* Pagination.module.css */
.is-active {
/* ⚠️ Complicado de acceder */
}
// Tienes que usar corchetes
<a className={styles['is-active']}>Link</a>
// ✅ Mejor: usa camelCase
// .isActive { }
<a className={styles.isActive}>Link</a>
Convención: Usa camelCase para nombres de clases en CSS Modules.
❌ Tratar de usar clases globalmente
/* Pagination.module.css */
.pagination {
}
// ❌ No funciona: la clase real es "Pagination_pagination__a1b2c"
<nav className="pagination">
// ✅ Usa el objeto styles
<nav className={styles.pagination}>
Escapando al ámbito global: :global()
A veces necesitas estilos globales dentro de un CSS Module:
/* Pagination.module.css */
.pagination {
/* Este estilo es local */
display: flex;
}
/* Este estilo será global */
:global(.container) {
max-width: 1200px;
margin: 0 auto;
}
/* Afecta a todos los enlaces globalmente */
:global(a) {
color: blue;
}
Uso común: Estilos para librerías externas que inyectan HTML:
.myComponent {
/* Estilos locales */
}
/* Estilar elementos inyectados por una librería */
:global(.external-library-class) {
color: red;
}
Lo que hemos visto en esta clase
- 📦 CSS Modules - CSS con ámbito local para componentes
- 🏷️ Convención
.module.css- Cómo nombrar los archivos - 🔒 Encapsulación - Evitar colisiones de nombres
- 🎯 Import y uso -
import styles from './Pagination.module.css' - 🔧 Cómo funcionan - Transformación de nombres a clases únicas
- ✨ Ventajas - Autocompletado, mantenibilidad, sin colisiones
- 🌐 Global vs Modules - Cuándo usar cada uno
- 🎭 :global() - Escapar al ámbito global cuando sea necesario
En la próxima clase seguiremos explorando más conceptos de React que te ayudarán a construir aplicaciones más robustas y mantenibles.
💡 Recuerda: Usa CSS Modules para estilos de componentes específicos y CSS global para estilos base y utilidades. Los CSS Modules te dan encapsulación y evitan colisiones de nombres automáticamente.