Panda CSS 2.0: El Compilador Reescrito en Rust y Cómo Migrar sin Romper tu Build

Panda CSS 2.0 reemplaza su compilador basado en TypeScript por un motor escrito en Rust sobre Oxc: mantienes la misma API css(), pero la migración es manual. La versión 2.0.0 salió el 29 de septiembre de 2026 y hoy npm sirve la 2.1.2. No hay codemod, y necesitas Node 22 y un proyecto ESM antes de empezar.

¿Qué es Panda CSS y qué tiene que ver con Chakra UI?

Panda CSS es un framework de CSS-in-JS zero-runtime y con tipado estricto, pensado para construir sistemas de diseño. Lo desarrolla el equipo de Chakra UI, con Segun Adebayo a la cabeza; el repositorio vive en chakra-ui/panda, tiene 6.2k estrellas en GitHub y usa licencia MIT. Escribes estilos como objetos tipados con css(), recipes, patterns y props de estilo en JSX. En tiempo de build, Panda los extrae y genera CSS atómico, así que el navegador recibe una hoja de estilos normal y no un runtime de estilos.

Los tokens, las condiciones y los recipes viven en panda.config.ts, y a partir de esa configuración Panda genera una carpeta styled-system con helpers tipados. Si vienes de Chakra UI, la documentación oficial incluye una guía de migración específica, junto con guías desde Tailwind CSS, StyleX, Emotion, Styled Components y vanilla-extract.

¿Qué cambia en Panda CSS 2.0?

Cambia el motor; la forma de escribir estilos no.

En la v1, la extracción pasaba por ts-morph, un envoltorio del compilador completo de TypeScript, y un intérprete de JavaScript evaluaba los valores constantes. En la v2, Oxc analiza cada archivo una sola vez, y ese único parseo se comparte entre imports, llamadas a css(), JSX y resolución de identificadores. Los archivos que nunca importan Panda, incluida la mayor parte de node_modules, se saltan por completo. El CSS se emite de forma nativa en Rust en lugar de pasar por PostCSS.

El motor se distribuye de dos maneras:

Binding Paquete Dónde corre
Nativo (NAPI) @pandacss/compiler CLI, Vite, PostCSS
WebAssembly @pandacss/compiler-wasm (~490 KB gzip) Navegador, playground

Ambos envuelven los mismos crates de Rust, así que un build en Node y uno en el navegador producen exactamente el mismo CSS.

¿Cuánto más rápido es Panda CSS 2.0?

Estas cifras son benchmarks del propio equipo de Chakra, medidos sobre sus fixtures, que llegan hasta un proyecto sintético de mil archivos:

Medición v1 v2
Extracción en frío — 15–37× más rápida
Parseo en frío, 100 archivos 187 ms 7,5 ms
Re-parseo en modo watch, por archivo ~650 µs menos de 2 µs
Paso de parseo, proyecto Next.js 762 ms 31 ms
staticCss, config de ~29.000 reglas 25,7 s ~0,3 s
Instanciaciones de tipos generados ~2.600 ~20

El equipo también reporta entre un 21 y un 25 por ciento menos de memoria en tsc, entre un 40 y un 60 por ciento menos de tiempo de type-check, y css() y recipes con memoización, hasta unas 4 veces más rápidos para los estilos que se resuelven en runtime.

¿Qué resuelve el nuevo motor que la v1 no podía?

La v2 sigue los imports. En la v1, un valor de estilo importado desde otro archivo no producía CSS. La v2 lee imports con nombre, re-exports, barrels con export * y alias de rutas de tsconfig, y genera la regla. También resuelve en build miembros de objetos, aritmética en template literals y las dos ramas de un ternario. Si agregaste entradas en staticCss solo porque la v1 no veía un import, ya puedes quitarlas.

Hay dos formas de import que todavía no se siguen: los imports por defecto y los de namespace (import * as theme). Usa imports con nombre.

¿Cómo migrar a Panda CSS 2.0?

Tu panda.config.ts se mantiene, y los binarios panda y pandacss conservan sus nombres. El resto es una lista de cambios que conviene hacer en este orden. Los detalles siguen la guía oficial Upgrading to v2 tal como estaba al 8 de octubre de 2026.

1. Pasa a Node 22 y ESM

La v2 es solo ESM y no tiene build de CommonJS. Define "type": "module" en package.json (o usa .mjs) y convierte cualquier require():

// Antes
const { defineConfig } = require('@pandacss/dev')
// Después
import { defineConfig } from '@pandacss/dev'

Deja postcss.config.cjs como está: sigue en CommonJS a propósito, porque Next.js todavía espera ese archivo en CJS.

2. Actualiza todos los paquetes @pandacss/* juntos

npm install @pandacss/dev@latest

Todos los paquetes @pandacss/* se publican con una sola versión: no mezcles paquetes de la v1 con la v2. Actualiza también a la misma versión las integraciones que uses: @pandacss/postcss, @pandacss/vite, @pandacss/webpack, @pandacss/rollup o @pandacss/bun.

3. Haz un build limpio

panda build --clean      # codegen + cssgen en una pasada
panda codegen --clean    # o, si solo ejecutas codegen

Solo necesitas --clean una vez, pero no te lo saltes. La v2 no borra los archivos que ya no genera, y la v1 escribía .mjs donde la v2 escribe .js. Vite resuelve index.mjs antes que index.js, así que un archivo sobrante de la v1 puede ganarle al nuevo sin que te des cuenta.

4. Ejecuta panda doctor

panda doctor reemplaza a inspect, validate e info con un único chequeo de salud del proyecto. Agrega --json si lo vas a usar en scripts. Es lo más parecido a una herramienta automática de migración que trae la v2.

5. Corrige la configuración, empezando por los cambios silenciosos

Dos de estos cambios no producen ningún error:

  • Los presets ya no se agregan solos. La v1 inyectaba @pandacss/preset-base automáticamente; la v2 no. Si tu configuración lista solo tus propios presets, pierdes las utilidades y condiciones base. El build sigue funcionando, con advertencias unknown_condition. Si te faltan componentes como Flex, es por esto.

    export default defineConfig({ presets: ['@pandacss/preset-base', '@acme/preset'] })
    
    
  • theme.containerSizes ahora es theme.containers. La clave antigua se ignora sin advertencia, y todas las condiciones de contenedor @name/size dejan de funcionar.

El resto de los cambios:

  • Los hooks se movieron a plugins. Cada hook vive ahora en un plugin con nombre, con un filter y un handler. cssgen:done pasó a ser de solo lectura: puedes leer el CSS, pero no reescribirlo. Si lo usabas para quitar tokens o keyframes sin uso, cámbialo por optimize.removeUnusedTokens y optimize.removeUnusedKeyframes.
  • createStyleContext se dividió en dos: createSlotRecipeContext para slot recipes (sva) y createRecipeContext para config recipes (cva).
  • Se eliminó la sintaxis de template literal. Quita la opción syntax y reescribe css`color: red;` como css({ color: 'red' }).
  • defineParts ya no existe. Escribe tú el selector [data-part="…"] o usa defineSlotRecipe.
  • Se eliminaron nueve opciones de configuración: studio, eject, emitTokensOnly, gitignore, clean, watch, poll, lightningcss y browserslist.
  • Cambiaron los imports de tipos. @pandacss/dev ahora exporta solo Config, Preset y UserConfig; el resto viene de @pandacss/types.
  • Ya no necesitas @pandacss/plugin-vue ni @pandacss/plugin-svelte. El compilador lee archivos .vue, .svelte y .astro directamente.

¿Panda CSS 2.0 rompe tu CI?

Puede romperlo, pero por tus scripts de package.json y no por tus estilos. Revisa los scripts con el mismo cuidado que la configuración:

v1 v2
panda codegen --silent panda codegen --log-level silent
panda cssgen "src/**/*.tsx" panda cssgen --include "src/**/*.tsx"
--cpu-prof --profile (escribe trace.json y timings.json, no .cpuprofile)
panda inspect / validate / info panda doctor
panda mcp npx -y @pandacss/mcp
panda ship panda lib
--poll eliminado

El cambio de los globs posicionales es el más peligroso. En cssgen, analyze y debug, un glob sobrante no falla: Panda simplemente lo ignora y escanea la lista include de tu configuración. El build termina bien, pero escanea archivos distintos de los que esperabas.

Además de los scripts, tu imagen de CI necesita Node 22 o superior.

¿Qué cambia en el CSS generado?

El CSS se mantiene igual que en la v1, salvo una lista corta de cambios deliberados. Conócelos antes de comparar la salida:

  • Los breakpoints usan sintaxis de rango, @media (width >= 48rem), en lugar de min-width.
  • Los overrides de borde se ordenan por propiedad, no por orden en el código. Un borderWidth que aplica a todos los lados ahora siempre le gana a un borderInlineEnd de un solo lado. Cuando un override tiene que ganar, usa la propiedad larga: borderInlineEndWidth: '0'.
  • Desaparece el reset universal de variables. Las 34 variables que la v1 ponía en cada elemento ahora se registran con @property y solo se emiten cuando se usan. Esto requiere Chrome 85+, Safari 16.4+ o Firefox 128+. Activa optimize.propertyFallback: true si das soporte a navegadores más antiguos.
  • sizes.prose es más angosto en @pandacss/preset-panda: 60ch en lugar de 65ch.
  • scrollbarWidth acepta palabras clave (auto | thin | none), no tokens de tamaño.

Si migras de forma gradual, ojo con las cascade layers. Panda emite su CSS dentro de @layer, y según la especificación de CSS cualquier regla sin capa le gana a todas las reglas con capa. Tus estilos heredados van a ganarle a los componentes ya convertidos, a menos que quites el envoltorio de capa con postcss-cascade-layers o mantengas una frontera clara por ruta o por directorio.

¿Qué vale la pena activar después de migrar?

  • El plugin de ESLint. Valida con el mismo motor de extracción que usa tu build, y también corre bajo oxlint:

    // eslint.config.jsimport panda from '@pandacss/eslint-plugin'export default [panda.configs.recommended]
    
    

    Incluye reglas como no-invalid-token-paths, prefer-token, no-shorthand-longhand-mix, no-deprecated y un consistent-property-style con autofix.

  • viewTransition() permite estilizar la View Transitions API desde tu styled system.

  • firstThatWorks() emite un valor moderno con su fallback, por ejemplo firstThatWorks('100dvh', '100vh').

  • keyframes() y positionTry() definen animaciones y fallbacks de anchor positioning junto al componente que los usa, con tree-shaking según lo que el build realmente referencia.

  • panda lib y designSystem te permiten publicar un sistema de diseño como paquete npm y consumirlo sin volver a extraerlo en la app.

  • transform: true en los plugins de Vite, webpack, Rollup o Bun reescribe las llamadas estáticas a css() como strings de clases, de modo que el runtime de estilos desaparece del bundle.

¿Panda CSS o Tailwind?

Ambos generan CSS atómico en tiempo de build; la diferencia está en dónde escribes los estilos. Tailwind usa clases utilitarias en el markup. Panda usa objetos de estilo y props tipados que se validan contra tus tokens, y con la v2 el plugin de ESLint hace cumplir esos tokens también al momento de hacer lint. Si quieres evaluar el cambio, la documentación de Panda incluye una guía de migración desde Tailwind CSS.


¿Y tú? ¿Qué cambio de la v2 te preocupa más al migrar tu proyecto?

¿Qué usas hoy para los estilos de tus proyectos?
  • Tailwind CSS
  • Panda CSS
  • CSS-in-JS con runtime (Emotion, Styled Components)
  • Prefiero otra opción (cuéntanos cuál)
0 votantes

Comenta abajo o, si este artículo te llegó por correo, responde directamente al email: tu respuesta se publica aquí.

Relacionado: Feature Friday: Vite 6.0 - La Revolución del Build Tools que Redefine la Velocidad