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 .button se 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 (a dentro de .pagination) - Esto funciona con herramientas modernas como Vite
  • Usamos &:hover para 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: .isActive es ú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.