CSS Modules vs Tailwind en Next.js: comparativa completa con ejemplos y mejores prácticas

DUGLAS MORENODUGLAS MORENO 👁 9
🎧 Escuchá el resumen · 1:00

Introducción

Cuando arrancás un proyecto con Next.js, una de las primeras decisiones que tenés que tomar es cómo vas a manejar los estilos. Dos de las opciones más populares son CSS Modules y Tailwind CSS. Cada una tiene su filosofía, su forma de trabajar y sus trade‑offs. En este artículo vamos a profundizar en qué son, cómo se integran en Next.js, cuándo conviene usar cada una y qué errores evitar. Al final vas a tener una visión clara para elegir la que mejor se adapte a tu flujo de trabajo y a las necesidades de tu aplicación.


¿Qué son CSS Modules?

CSS Modules es una técnica que permite escribir CSS tradicional pero con alcance local automático. Cada nombre de clase o animación que definís en un archivo .module.css se transforma en un identificador único (generalmente un hash) que evita colisiones globales. De esta forma podés reutilizar nombres como button o card en diferentes componentes sin temor a que se pisuen.

Características principales

  • Encapsulamiento: los estilos solo afectan al componente que los importa.
  • Sintaxis conocida: seguís escribiendo CSS plano, lo que facilita la transición desde proyectos tradicionales.
  • Composición: podés usar la palabra clave composes para reutilizar reglas dentro del mismo archivo.
  • Source Maps: al estar basado en CSS, los devtools muestran el nombre original de la clase, facilitando el debug.

¿Qué es Tailwind CSS?

Tailwind es un framework utility‑first. En lugar de escribir reglas CSS personalizadas, aplicás clases predefinidas directamente en el markup. Cada clase representa una propiedad CSS individual (por ejemplo flex, pt-4, bg-blue-500). Tailwind genera un archivo CSS optimizado que incluye solo las clases que realmente usás en tu proyecto, gracias a su motor de purgado (purge).

Características principales

  • Velocidad de desarrollo: no tenés que cambiar de contexto entre HTML y CSS; todo está en el mismo archivo.
  • Consistencia de diseño: al usar una escala de valores predefinida (spacing, colores, tipografía) evitás valores arbitrarios.
  • Personalización: el archivo tailwind.config.js permite ajustar la paleta, los breakpoints, los plugins, etc.
  • Tipado opcional: con el modo JIT (Just‑In‑Time) de Tailwind 3+, las clases se generan bajo demanda, lo que hace que el build sea rápido incluso en proyectos grandes.

Configuración en Next.js

Ambas soluciones se integran de forma sencilla en un proyecto Next.js recién creado con create-next-app. A continuación vemos los pasos necesarios para cada una.

CSS Modules

Next.js ya incluye soporte out‑of‑the‑box para CSS Modules. Solo tenés que nombrar tus archivos con la extensión .module.css y importarlos en tus componentes.

# Si todavía no tenés un proyecto Next.js
npx create-next-app@latest mi-proyecto --ts   # opcional con TypeScript
cd mi-proyecto

Luego creá un archivo styles/Button.module.css:

/* styles/Button.module.css */
.button {
  background-color: #0070f3;
  color: white;
  border: none;
  padding: 0.5rem 1rem;
  border-radius: 0.25rem;
  cursor: pointer;
}

.button:hover {
  background-color: #005bb5;
}

Y usalo en un componente:

// components/Button.tsx
import styles from '../styles/Button.module.css';

export default function Button({ children, onClick }: { children: React.ReactNode; onClick?: () => void }) {
  return (
    <button className={styles.button} onClick={onClick}>
      {children}
    </button>
  );
}

Tailwind CSS

Para usar Tailwind necesitamos instalar las dependencias y configurar el archivo postcss.config.js y tailwind.config.js. Next.js tiene un plugin oficial que simplifica todo.

npm i -D tailwindcss postcss autoprefixer
npx tailwindcss init -p   # crea tailwind.config.js y postcss.config.js

Luego editá tailwind.config.js para indicar qué archivos deben ser escaneados:

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    './pages/**/*.{js,ts,jsx,tsx}',
    './components/**/*.{js,ts,jsx,tsx}',
  ],
  theme: {
    extend: {},
  },
  plugins: [],
};

Importá el base de Tailwind en styles/globals.css (que ya viene con el proyecto):

/* styles/globals.css */
@tailwind base;
@tailwind components;
@tailwind utilities;

Finalmente, reiniciá el dev server y ya podés usar clases Tailwind en cualquier componente:

// components/ButtonTailwind.tsx
export default function ButtonTailwind({ children, onClick }: { children: React.ReactNode; onClick?: () => void }) {
  return (
    <button
      className="bg-blue-500 hover:bg-blue-600 text-white font-medium py-2 px-4 rounded"
      onClick={onClick}
    >
      {children}
    </button>
  );
}

Ejemplos prácticos con salida esperada

A continuación vamos a mostrar dos componentes idénticos (un botón primario) implementados uno con CSS Modules y otro con Tailwind. Además incluimos un pequeño bloque de JavaScript ejecutable que muestra cuál sería la cadena de clases resultante en cada caso, para que veáis la diferencia de forma inmediata.

Ejemplo 1: Botón con CSS Modules

Archivo styles/Button.module.css

/* styles/Button.module.css */
.primaryBtn {
  background-color: #0070f3;
  color: #fff;
  border: none;
  padding: 0.5rem 1rem;
  border-radius: 0.25rem;
  font-weight: 600;
  cursor: pointer;
  transition: background-color 0.2s ease;
}

.primaryBtn:hover {
  background-color: #005bb5;
}

.primaryBtn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

Componente

// components/PrimaryButton.tsx
import styles from '../styles/Button.module.css';

export default function PrimaryButton({ children, disabled, onClick }: {
  children: React.ReactNode;
  disabled?: boolean;
  onClick?: () => void;
}) {
  return (
    <button
      className={`${styles.primaryBtn} ${disabled ? styles.primaryBtn : ''}`}
      disabled={disabled}
      onClick={onClick}
    >
      {children}
    </button>
  );
}

Qué se renderiza en el DOM (ejemplo de salida esperada):

<button class="primaryBtn_abc123">Click me</button>

Donde primaryBtn_abc123 es el hash generado por CSS Modules (el valor exacto cambia en cada build).

Ejemplo 2: Botón con Tailwind

Componente

// components/PrimaryButtonTailwind.tsx
export default function PrimaryButtonTailwind({ children, disabled, onClick }: {
  children: React.ReactNode;
  disabled?: boolean;
  onClick?: () => void;
}) {
  return (
    <button
      className={
        `bg-blue-500 hover:bg-blue-600 text-white font-medium py-2 px-4 rounded transition-colors duration-200 ${
          disabled ? 'opacity-50 cursor-not-allowed' : ''
        }`
      }
      disabled={disabled}
      onClick={onClick}
    >
      {children}
    </button>
  );
}

Qué se renderiza en el DOM (salida esperada):

<button class="bg-blue-500 hover:bg-blue-600 text-white font-medium py-2 px-4 rounded transition-colors duration-200">Click me</button>

Si el botón está deshabilitado, se añade opacity-50 cursor-not-allowed.

Bloque ejecutable: comparando las cadenas de clases

Para ilustrar la diferencia de forma inmediata, acá tenés un pequeño script de Node que simula cómo se formarían las clases en cada enfoque. Este bloque es runnable porque solo usa console.log y no necesita acceso a archivos ni a la red.

// Simulación de generación de clases
const base = 'bg-blue-500 hover:bg-blue-600 text-white font-medium py-2 px-4 rounded transition-colors duration-200';
const disabled = true;

const tailwindClasses = `${base} ${disabled ? 'opacity-50 cursor-not-allowed' : ''}`;
console.log('Tailwind classes:', tailwindClasses.trim());

// CSS Modules: el nombre local se transforma con un hash (simulado)
const moduleName = 'primaryBtn';
const hash = 'a1b2c3'; // en la práctica esto lo genera webpack/postcss
const moduleClass = `${moduleName}_${hash}`;
console.log('CSS Modules class:', moduleClass);

Salida esperada al ejecutar el bloque:

Tailwind classes: bg-blue-500 hover:bg-blue-600 text-white font-medium py-2 px-4 rounded transition-colors duration-200 opacity-50 cursor-not-allowed
CSS Modules class: primaryBtn_a1b2c3

Ventajas de CSS Modules

  1. Previsibilidad y control total
    Como escribís CSS puro, tenés control absoluto sobre cada propiedad. No dependés de una utilidad que quizá no haga exactamente lo que querés; podés agregar cualquier valor arbitrario (por ejemplo background-color: #f39c12;).

  2. Facilidad de migración desde proyectos legacy
    Si venís de un código base con SASS, LESS o CSS plano, la transición a CSS Modules es casi inmediata: solo renombrás los archivos a *.module.css y los importás.

  3. Mejor depuración en DevTools
    El nombre original de la clase se mantiene en el source map, por lo que al inspeccionar un elemento veés algo como primaryBtn_abc123 y podés buscar fácilmente el origen en tu editor.

  4. No depende de un archivo de configuración gigante
    No tenés que mantener un tailwind.config.js con cientos de líneas si solo necesitás unos pocos estilos personalizados.

  5. Compatibilidad con CSS avanzado
    Podés usar @media, @keyframes, @supports, variables CSS (--my-var: 10px;) y cualquier futura característica del estándar sin esperar a que Tailwind la añada como utility.


Ventajas de Tailwind CSS

  1. Velocidad de desarrollo
    Al escribir las clases directamente en el markup, reducís el ida y vuelta entre archivos. Muchas veces basta con agregar mt-4 flex items-center y ya tenés el layout listo.

  2. Escalabilidad sin aumento del tamaño del CSS
    Gracias al modo JIT y al purge, solo se genera el CSS que realmente usás. Incluso en proyectos muy grandes el archivo final suele quedar bajo los 50 KB gzipped.

  3. Consistencia de diseño garantizada
    La escala de espacios (p-2, p-4, p-6), colores (blue-500, gray-800) y tipografía (text-base, text-lg) evita que aparezcan valores mágicos como margin: 13px; que rompen la armonía visual.

  4. Facilidad de tema oscuro y múltiples variantes
    Con el modo dark: podés definir fácilmente estilos que cambian según el tema, sin necesidad de crear archivos CSS separados.

  5. Ecosistema rico de plugins
    Existen plugins para forms, tipografía, aspecto, filtros, etc. Que se instalan con un solo npm i y se añaden al arreglo de plugins en tailwind.config.js.


Desventajas y errores comunes

CSS Modules

  • Sobrecarga de nombres largos
    En inspección, las clases pueden verse algo como Button_primaryBtn__3QdmY. Esto no afecta el rendimiento pero puede dificultar la lectura rápida del DOM.
  • Duplicación accidental
    Si olvidas usar el archivo .module.css y importás un CSS normal, perdés el encapsulamiento y podés generar conflictos.
  • Falta de utilidades predefinidas
    Tenés que escribir todo el CSS vos mismo, lo que puede llevar a más tiempo en tareas repetitivas (por ejemplo, escribir los mismos border-radius en varios lugares).

Tailwind

  • Curva de aprendizaje inicial
    Al principio puede resultar abrumador memorizar todas las utilities; sin embargo, la documentación y el autocomplete en IDEs lo mitigan.
  • HTML con muchas clases
    Los elementos pueden terminar con largas cadenas de clases, lo que some consideren poco estético. Se puede mitigar con extracción de componentes o con la directiva @apply (aunque esta última va en contra de la filosofía utility‑first).
  • Dependencia de la configuración de purge
    Si no configurás bien el content en tailwind.config.js, podés perder estilos en producción porque el purge los elimina pensando que no se usan.
  • Limitaciones para valores arbitrarios complejos
    Aunque Tailwind permite valores arbitrarios con corchetes (w-[13px]), para cosas muy específicas (gradientes complejos, transformaciones 3D) a veces es más claro escribir CSS tradicional.

Buenas prácticas

Para CSS Modules

  • Nombrado consistente: usá una convención como nombreComponente__estado (p.ej. card__header, card__isVisible).
  • Separar preocupaciones: mantené los archivos de estilos lo más pequeños posible; un archivo por componente o por grupo de componentes estrechamente relacionados.
  • Aprovechar composes: si tenés variaciones de un mismo estilo (por ejemplo, un botón primario y uno secundario que solo difiere en color), definí la base y compósala:
    .baseBtn { padding: 0.5rem 1rem; border-radius: 0.25rem; }
    .primaryBtn { composes: baseBtn; background-color: #0070f3; color: white; }
    .secondaryBtn { composes: baseBtn; background-color: #e0e0e0; color: #333; }
  • No abusar de selectores anidados: aunque CSS Modules permite anidamiento, mantenelo sencillo para evitar especificidades altas.

Para Tailwind

  • Aprovechar el JIT: asegurate de que esté activo (por defecto lo está en Tailwind 3+).
  • Extraer componentes reutilizables: si notás que se repite la misma cadena de clases en varios lugares, creá un componente React que lo encapsule.
  • Usar @layer para personalizaciones: en vez de editar directamente el archivo de utilidades, agregá tus propias clases en tailwind.css bajo @layer components o @layer utilities.
  • Configurar bien el content: incluyé todos los directorios donde puedan aparecer clases (por ejemplo, ./src/**/*.{js,ts,jsx,tsx} si tenés una estructura src).
  • Evitar @apply en excess: si bien es útil para mover estilos a CSS, sobreusarlo pierde la ventaja de tener todo en el markup.

Conclusión

Tanto CSS Modules como Tailwind CSS son excelentes opciones para estilizar aplicaciones Next.js, pero cada una brilla en contextos diferentes.

  • Elegí CSS Modules si:

    • Querés control total sobre cada propiedad y te sintieras cómodo escribiendo CSS tradicional.
    • Tenés un equipo que proviene de proyectos con preprocesadores (SASS, LESS) y buscás una migración sin fricción.
    • Necesitás características avanzadas de CSS (variables, animaciones complejas, consultas de contenedor) que aún no tienen utilities en Tailwind.
  • Elegí Tailwind si:

    • Buscás acelerar el desarrollo y reducir el cambio de contexto entre HTML y CSS.
    • Querés un diseño altamente consistente sin tener que definir escalas de espacios, colores o tipografía manualmente.
    • Tenés un proyecto grande donde el tamaño del CSS final es crítico y apreciás el purge automático de Tailwind.
    • Valorás el ecosistema de plugins y la facilidad de implementar temas oscuros o múltiples variantes con simples prefixes como dark: o hover:.

En la práctica, muchos equipos combinan ambas approaches: usan Tailwind para el layout y los componentes de UI comunes, y recurren a CSS Modules (o incluso a CSS en JS) cuando necesitan estilos altamente específicos o animaciones complejas. La clave es conocer las fortalezas de cada herramienta y aplicarlas donde aporten más valor.

¡Ahora tenés la información necesaria para tomar una decisión informada y empezar a estilizar tus próximos proyectos Next.js con confianza!


¿Te quedó alguna duda? Dejála en los comentarios y con gusto te ayudo a aclararla.

Si te gustó este artículo, compartilo con tu equipo y seguí explorando más sobre desarrollo frontend en nuestro blog.

Comentarios (0)

Sé el primero en comentar.

Dejá tu comentario