Saltar al contenido principal
Volver al blogCopiar Markdown
·Actualizado ·7 min de lectura
·tailwindcsscssfrontendastrowebdev

Tailwind CSS 4: motor en Rust y rebuilds en microsegundos

Tailwind CSS 4 reescribió el motor en Rust y Tailwind publica sus propias mediciones. Qué se rompe al migrar desde v3, el bloque @theme y @tailwindcss/vite. →

Óscar Gallego

Ingeniero de software · Málaga

Tailwind CSS 4 performance - Rust engine optimization illustration
En este artículo

Tailwind CSS 4 no es una actualización menor. Es una reescritura completa del motor en Rust, y rompe la compatibilidad con v3 en sitios que te van a costar una tarde cada uno.

La velocidad la mide Tailwind; la rotura la vas a notar tú. Esto es lo que conviene saber antes de migrar.

Qué cambia en v4, en 30 segundos

El framework se ha reconstruido alrededor de un nuevo compilador escrito en Rust. Según el anuncio de Tailwind v4, los builds completos son hasta 5x más rápidos y los rebuilds incrementales que no añaden CSS nuevo, más de 100x más rápidos. En su tabla de medianas sobre Catalyst, el build completo pasa de 378ms a 100ms y un rebuild que no compila CSS nuevo, de 35ms a 192µs. Encima, una configuración “CSS-first” que elimina la necesidad de un archivo tailwind.config.js.

Las piezas que importan:

  • Nuevo motor en Rust: builds completos más rápidos, y los rebuilds incrementales que no añaden CSS nuevo terminan en microsegundos, según las mediciones de Tailwind enlazadas arriba.
  • Configuración en CSS: tailwind.config.js ya no es obligatorio. CSS-first es ahora el modo por defecto, con variables CSS nativas y la directiva @theme dentro de tu archivo CSS principal. Un config JS sigue funcionando si lo cargas de forma explícita con @config "./tailwind.config.js".
  • Integración nativa con Vite: se conecta directamente como plugin de Vite (@tailwindcss/vite), sin wrappers específicos del framework como @astrojs/tailwind.
  • Plugins en CSS: ahora se importan directamente en el CSS con la directiva @plugin.
  • Container queries en el core: lo que antes requería un plugin ahora viene integrado.

Lo que no medí

No repito aquí mis tiempos de v3 contra v4, porque una sola ejecución en una sola máquina no merece citarse: las cifras de rendimiento de este post son las de Tailwind, con su fuente. Si quieres tu número, mide tú el build antes y después en tu máquina.

tailwind.config.js ya no es obligatorio

El cambio más radical es que el archivo de configuración JavaScript ya no es obligatorio. CSS-first es el modo por defecto, y si aun así quieres un config JS lo cargas de forma explícita con @config "./tailwind.config.js". Ya no se detecta automáticamente.

Antes (v3)

// tailwind.config.js
module.exports = {
  theme: {
    extend: {
      colors: {
        primary: '#3b82f6',
        secondary: '#8b5cf6',
      },
      fontFamily: {
        sans: ['Inter', 'system-ui', 'sans-serif'],
      },
      spacing: {
        '128': '32rem',
      }
    }
  },
  plugins: [
    require('@tailwindcss/typography'),
    require('@tailwindcss/forms'),
  ],
}

Después (v4)

/* src/styles/global.css */
@import "tailwindcss";

@theme {
  /* Colores personalizados */
  --color-primary: #3b82f6;
  --color-secondary: #8b5cf6;

  /* Fuentes */
  --font-sans: 'Inter', system-ui, sans-serif;

  /* Spacing custom */
  --spacing-128: 32rem;

  /* Breakpoints */
  --breakpoint-3xl: 1920px;
}

/* Plugins ahora son CSS nativo */
@plugin "@tailwindcss/typography";
@plugin "@tailwindcss/forms";

Lo que ganas:

  1. Type-safety mejorado: las variables CSS tienen mejor autocomplete en VSCode.
  2. Hot reload instantáneo: los cambios en el theme se aplican sin recompilar.
  3. Menos abstracción: lo que ves en el CSS es lo que obtienes.

El setup correcto en Astro (y el que rompe)

Antes de nada: no uses @astrojs/tailwind con Tailwind v4. Causa conflictos.

Setup para Astro + Tailwind v4

npm install tailwindcss @tailwindcss/vite
// astro.config.mjs
import { defineConfig } from 'astro/config';
import tailwindcss from '@tailwindcss/vite';

export default defineConfig({
  vite: {
    plugins: [tailwindcss()],
  },
});
/* src/styles/global.css */
@import "tailwindcss";
---
// src/layouts/Layout.astro
import '@/styles/global.css';
---

Y la versión que rompe:

// MAL
import tailwind from '@astrojs/tailwind';

export default defineConfig({
  integrations: [tailwind()], // Esto rompe Tailwind v4
});

@apply dentro de @keyframes: el cambio que más duele

El cambio más doloroso de v4: @apply no funciona dentro de @keyframes.

El caso que falla

/* MAL: esto ROMPE en Tailwind v4 */
@keyframes fade-in {
  from {
    @apply opacity-0 scale-95;
  }
  to {
    @apply opacity-100 scale-100;
  }
}

Error:

@apply is not supported within at-rules like @keyframes

El arreglo: CSS vanilla

/* BIEN: usa CSS estándar en keyframes */
@keyframes fade-in {
  from {
    opacity: 0;
    scale: 0.95;
  }
  to {
    opacity: 1;
    scale: 1;
  }
}

/* Luego aplica la animación */
.fade-enter {
  animation: fade-in 0.3s ease-out;
}

La otra trampa: @apply con dark mode en Astro

<style>
  /* MAL: esto puede fallar en algunos casos */
  .card {
    @apply bg-white dark:bg-gray-900;
  }
</style>

Error:

The `dark:bg-gray-900` class does not exist

El arreglo son variables CSS más clases regulares:

<div class="card bg-white dark:bg-gray-900">
  <!-- contenido -->
</div>

<style>
  /* Solo estilos que no dependen de Tailwind */
  .card {
    border-radius: 0.5rem;
    box-shadow: 0 1px 3px rgba(0,0,0,0.1);
  }
</style>

Cinco problemas al migrar

1. Plugins desactualizados

Muchos plugins de v3 directamente no funcionan en v4.

Solución temporal:

// Usa la versión v4-compatible
npm install @tailwindcss/typography @tailwindcss/forms

2. Custom utilities con addUtilities()

Antes (v3):

// tailwind.config.js
const plugin = require('tailwindcss/plugin');

module.exports = {
  plugins: [
    plugin(({ addUtilities }) => {
      addUtilities({
        '.scrollbar-hide': {
          '-ms-overflow-style': 'none',
          'scrollbar-width': 'none',
          '&::-webkit-scrollbar': {
            display: 'none'
          }
        }
      });
    })
  ]
}

Después (v4):

/* src/styles/global.css */
@layer utilities {
  .scrollbar-hide {
    -ms-overflow-style: none;
    scrollbar-width: none;

    &::-webkit-scrollbar {
      display: none;
    }
  }
}

3. Arbitrary values

v3:

<div class="w-[calc(100%-2rem)]">

v4: (Funciona igual, pero ahora con mejor autocompletion)

<div class="w-[calc(100%-2rem)]">

4. Sintaxis de opacidad en colores

v3:

<div class="bg-blue-500/50">

v4: (Sin cambios, pero ahora más eficiente)

<div class="bg-blue-500/50">

5. Container queries

Antes requería un plugin. Ahora viene integrado en v4:

<div class="@container">
  <div class="@md:grid-cols-2">
    <!-- Se adapta al contenedor, no al viewport -->
  </div>
</div>

Dark mode sin el flash

Tailwind v4 mejora el soporte para dark mode basado en clases.

La configuración que recomiendo

@import "tailwindcss";

@variant dark (&:where(.dark, .dark *));

Esto permite:

<!-- Opción 1: Clase en root -->
<html class="dark">
  <div class="bg-white dark:bg-gray-900">
</html>

<!-- Opción 2: Clase en contenedor específico -->
<div class="dark">
  <p class="text-gray-900 dark:text-white">
</div>

Evitar FOUC (Flash of Unstyled Content)

<script>
  // Ejecuta ANTES del render
  if (localStorage.theme === 'dark' ||
      (!localStorage.theme && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
    document.documentElement.classList.add('dark');
  }
</script>

La herramienta de upgrade hace la mitad del trabajo

npx @tailwindcss/upgrade

La ejecutas y convierte tailwind.config.js a @theme en tu CSS, actualiza los imports en los archivos y detecta plugins incompatibles. Útil.

Lo que no hace: migrar custom utilities (eso te toca a ti) ni arreglar @apply dentro de keyframes (esos los reescribes a mano). Reserva tiempo para ambas cosas.

Trucos que siguen valiendo en v4

1. Purge agresivo (ya no hace falta)

En v3 tenías que configurar purge. En v4 el tree-shaking es automático y más inteligente. Una cosa menos.

2. Usar CSS nesting

/* Aprovecha el nesting nativo de CSS */
.card {
  @apply rounded-lg shadow-md;

  &:hover {
    @apply shadow-xl scale-105;
  }

  & .card-title {
    @apply text-xl font-bold;
  }
}

3. Optimizar fuentes con variables CSS

@theme {
  --font-sans: 'Inter var', system-ui, sans-serif;
  --font-mono: 'JetBrains Mono', monospace;
}

Luego usa font-variation-settings para weights dinámicos:

.dynamic-weight {
  font-variation-settings: 'wght' var(--font-weight);
}

¿Vale la pena migrar ahora?

Sí, si:

  • Tu build te resulta lento
  • Usas Vite/Astro (mejor integración)
  • Estás empezando un proyecto nuevo
  • Quieres aprovechar container queries nativas

Espera, si:

  • Tienes muchos plugins custom incompatibles
  • Tu equipo no puede dedicar tiempo a la migración
  • Dependes de @apply en keyframes intensivamente
  • Usas frameworks que aún no soportan v4 oficialmente

La migración duele al principio

Lo que te vas a encontrar es la lista de arriba: @apply en keyframes, plugins de v3 y utilities custom que reescribir. La ganancia de velocidad es la que publica Tailwind; el coste de migrar es el tuyo.

Si tu build te parece lento, ya sabes qué hacer con tu próxima tarde libre.

Lectura relacionada: el setup de este post asume Astro. Si todavía estás decidiendo el framework, empieza por por qué Astro envía tan poco JavaScript.

Fuentes

  • Tailwind Labs, Tailwind CSS v4.0 (benchmarks sobre Catalyst, medianas: build completo, rebuild incremental y rebuild sin CSS nuevo).

P.D. ¿Migrando un proyecto v3 y te has topado con algo que este post no cubre? Cuéntamelo en LinkedIn.

Última actualización:

Comparte este artículo

Artículos relacionados