htmx 4.0 ya está aquí
htmx 4.0 cambia la herencia, el swap de errores, el historial y los eventos, con consejos de migración y opciones para volver a htmx 2.
htmx 4.0.0 se publicó el 28 de agosto de 2026. Cambia varios comportamientos predeterminados que llevaban mucho tiempo vigentes: la herencia de atributos ahora es opcional (opt-in), las respuestas de error se insertan en el DOM y la caché de snapshots del historial ha desaparecido.
Si mantienes una aplicación con htmx 2, la pregunta práctica es si ese hx-confirm que colocaste en un contenedor hace dos años sigue protegiendo algo después de la actualización. No lo hace, a menos que añadas un modificador. Este artículo cubre qué se rompe, qué revierte cada ruptura y por qué la estrategia de publicación en npm implica que probablemente no tengas que actuar esta semana. Para saber qué es htmx y por qué hipermedia, el recorrido por htmx 2.0 cubre el terreno desde el que parte este artículo.
Puntos clave
- htmx 4 hace explícita la herencia de atributos mediante el modificador
:inherited, y establecerhtmx.config.implicitInheritanceentruerestaura el comportamiento de htmx 2 como puente de migración. - Solo las respuestas
204y304omiten el swap de forma predeterminada en htmx 4, por lo que un422renderizado en el servidor ahora llega al target en lugar de descartarse;htmx.config.noSwap = [204, 304, '4xx', '5xx']revierte ese comportamiento. - Los nombres de eventos siguen el patrón
htmx:phase:action, que no tiene clave de configuración: todos los listeners de htmx en tu JavaScript deben renombrarse, o bien instalar la extensiónhtmx-2-compat. - Renombra
hx-disableahx-ignoreantes de actualizar, porque htmx 4 reasigna el nombrehx-disablea la función que antes cumplíahx-disabled-elt. - htmx 2.x mantiene la etiqueta
latestde npm mientras que 4.0 queda bajonext, de modo que las URLs de CDN sin versión no se actualizan a la fuerza, y el anuncio indica que htmx 2 tendrá soporte indefinido.
¿Qué cambió en htmx 4?
htmx 4 migra las interioridades de las peticiones de la biblioteca de XMLHttpRequest a fetch(), y esa reescritura es lo que hizo posible el resto de la versión. Cambiar el transporte ya era de por sí un cambio incompatible, así que el equipo aprovechó la misma versión mayor para reajustar valores predeterminados que se habían ido acumulando desde htmx 1.
No llamas directamente a ninguna de las dos APIs cuando escribes htmx, así que el cambio de transporte en sí es invisible en tus plantillas. Las consecuencias aparecen en los bordes: los eventos de ciclo de vida específicos de XHR no tienen equivalente en fetch() y se eliminaron, y htmx 4 establece htmx.config.defaultTimeout en 60000, mientras que htmx 2 dejaba que una petición quedara colgada indefinidamente.
Sobre el número de versión: el creador de htmx, Carson Gross, había dicho que nunca habría un htmx 3, así que la versión salta directamente a 4.0 y la promesa sobrevive por un tecnicismo. Expuso el razonamiento en el ensayo que anunció la reescritura en noviembre de 2025.
La herencia de atributos ahora es explícita
La herencia en htmx 4 solo ocurre cuando la solicitas. Un atributo en un contenedor se aplica únicamente a ese contenedor, a menos que añadas el modificador :inherited, y el modificador funciona con cualquier atributo: hx-boost:inherited, hx-target:inherited, hx-confirm:inherited.
<!-- htmx 4: the confirm reaches both buttons -->
<div hx-confirm:inherited="Are you sure?">
<button hx-delete="/account">Delete My Account</button>
<button hx-put="/account">Update My Account</button>
</div>
Un valor en un hijo prevalece sobre el heredado de forma predeterminada. Usa :append cuando quieras combinar ambos, que es el caso de composición que suele confundir a la gente:
<div hx-vals:inherited="tenant:acme">
<button hx-post="/save" hx-vals:append="source:save-btn">Save</button>
</div>
Sin :append, el hx-vals propio del botón sustituye al heredado y tenant nunca llega al servidor. Cuando ningún ancestro establece el atributo, el valor añadido es el único valor enviado. Los nombres varían un poco según el atributo: la página de referencia de hx-disable documenta :merge para esa misma función de añadir al valor de un padre.
hx-inherit y hx-disinherit se han eliminado, ya que la adhesión explícita hace que ambos sean innecesarios. Si tus plantillas dependen del comportamiento antiguo, establece htmx.config.implicitInheritance en true para restaurarlo mientras migras. Trátalo como un puente, no como un destino.
Las respuestas de error se insertan de forma predeterminada
En htmx 4, una respuesta llega al target sea cual sea su código de estado, con solo 204 y 304 excluidos. Una página de validación 422 renderizada en el servidor ahora llega al target en lugar de descartarse silenciosamente, que es lo que las aplicaciones hipermedia querían desde el principio. Una respuesta de error HTTP también dispara un evento htmx:response:error.
El nuevo atributo hx-status enruta códigos individuales a su propio target y swap:
<form hx-post="/submit"
hx-target="#result"
hx-status:422="target:#validation-errors"
hx-status:5xx="target:#server-error"
hx-status:503="swap:none">
<input name="email">
<button type="submit">Submit</button>
</form>
htmx prueba primero el patrón más específico: el código exacto, después un patrón con el último dígito enmascarado, como 50x, y luego uno con los dos últimos enmascarados, como 5xx. Dentro del valor del atributo puedes establecer swap:, target:, select:, push:, replace: y transition:.
Si tu backend devuelve páginas de error que nunca se diseñaron para insertarse en el DOM, establece htmx.config.noSwap en [204, 304, '4xx', '5xx'] y recuperas el comportamiento de htmx 2.
La navegación hacia atrás ahora es una petición real
htmx 4 elimina la caché de snapshots del DOM en el cliente que respaldaba el historial en htmx 2. Al pulsar atrás, htmx vuelve a pedir la página al servidor y luego inserta lo que recibe en <body>, o en un elemento [hx-history-elt] si la página tiene uno.
El efecto práctico es que el botón atrás muestra lo que el servidor dice actualmente que es la página, no un snapshot congelado en el momento en que navegaste fuera. Eso elimina toda una clase de errores en los que scripts de terceros mutaban el DOM y el snapshot restaurado reproducía esas mutaciones dejando un estado roto. También significa que la navegación hacia atrás cuesta una petición.
El atributo hx-history desaparece junto con la caché. Si necesitas snapshots, la extensión del núcleo hx-history-cache los reintroduce como una opción a activar.
Los nombres de eventos siguen el patrón htmx:phase:action
Todos los eventos de ciclo de vida de htmx se renombraron con la forma htmx:phase:action[:sub-action]. El anuncio indica htmx:beforeRequest como htmx:before:request y htmx:beforeSwap como htmx:before:swap; htmx:afterSwap pasa a ser htmx:after:swap.
Este es el único cambio sin válvula de escape mediante configuración. Todos los listeners necesitan editarse:
// htmx 2
document.body.addEventListener('htmx:afterSwap', (e) => {
initTooltips(e.detail.target);
});
// htmx 4
document.body.addEventListener('htmx:after:swap', (e) => {
initTooltips(e.detail.target);
});
La mayoría de los eventos de error se consolidan en htmx:error, y las respuestas de error HTTP disparan htmx:response:error. Los eventos específicos de XHR simplemente desaparecen, ya que fetch() no expone equivalentes. Si editar listeners a mano es el grueso de tu migración, la extensión htmx-2-compat mapea los nombres de eventos antiguos a los nuevos y también restaura la herencia implícita y hx-ext.
¿Qué hay de nuevo en htmx 4, más allá de lo que se rompió?
Tres novedades de htmx 4 justifican por sí solas la actualización: los morph swaps, el elemento <hx-partial> y las extensiones de streaming reescritas. Los morph swaps están en el núcleo, así que las actualizaciones del DOM que preservan el estado ya no necesitan una extensión. El elemento <hx-partial> permite que una sola respuesta actualice varios targets, cada uno con su propio target y swap:
<hx-partial hx-target="#messages" hx-swap="beforeend">
<div>New message</div>
</hx-partial>
<hx-partial hx-target="#count">
<span>5</span>
</hx-partial>
Como el target y el estilo de swap viven en el propio partial, la respuesta declara qué hacer con cada fragmento, en lugar de dejarte deducirlo a partir de atributos hx-swap-oob dispersos por el marcado. Ten en cuenta que el orden de los out-of-band se invirtió en htmx 4: el contenido principal se inserta primero.
Las extensiones de streaming son el otro titular. Tanto la extensión de SSE como la de WebSocket se reconstruyeron para esta versión, y junto a ellas llega un conjunto nuevo: hx-multipart, hx-live, hx-targets, hx-ptag, hx-csp, hx-download, hx-prompt y hx-history-cache. Los atributos de conexión están espaciados por nombre, de modo que SSE se conecta con hx-sse:connect y los WebSockets con hx-ws:connect.
Actualizar a htmx 4, y por qué no hay prisa
Actualizar a htmx 4 empieza por el escáner: ejecútalo antes de planificar nada. npx htmx.org@4.0.0 upgrade-check -- ./path/to/project/root recorre tu proyecto e imprime cada patrón obsoleto con su archivo y número de línea, lo cual basta para dimensionar el trabajo en una tarde.
npx htmx.org@4.0.0 upgrade-check -- ./templates
npx htmx.org@4.0.0 upgrade-check --ext .vue ./path/to/project/root
De fábrica examina .html, .php, .js, .ts, .jinja, .jinja2, .j2, .erb y .hbs. Los formatos de componentes de archivo único no están en ese conjunto, así que las plantillas .vue, .svelte, .jsx y .astro quedan sin revisar a menos que pases --ext.
Haz un renombrado antes de tocar cualquier otra cosa: hx-disable pasa a ser hx-ignore, y hx-disabled-elt pasa a ser hx-disable. El nombre antiguo se reutiliza para otra función, así que si migras hx-disabled-elt primero, sobrescribirás atributos que todavía significan lo que significaban en htmx 2.
| Cambio | Predeterminado en htmx 4 | Qué restaura htmx 2 |
|---|---|---|
| Herencia de atributos | Explícita, mediante :inherited | htmx.config.implicitInheritance = true |
| Swap de respuestas de error | Solo 204/304 omiten el swap | htmx.config.noSwap = [204, 304, '4xx', '5xx'] |
| Historial | Nueva petición al servidor al ir atrás | Extensión hx-history-cache |
| Nombres de eventos | htmx:phase:action | Sin clave de configuración; extensión htmx-2-compat |
Y ahora la parte que decide si algo de esto es urgente: en npm, htmx 2.x mantiene el dist-tag latest y 4.0.0 se publica bajo next. El anuncio es explícito en que esto es deliberado, para que los sitios que cargan htmx desde una URL de CDN sin versión no se vean forzados a actualizarse con cambios incompatibles, y 2.x seguirá siendo latest hasta principios de 2027. 2.x seguirá teniendo soporte indefinidamente.
Eso se traduce en cuatro situaciones. Una URL de CDN sin versión sigue sirviendo 2.x hasta que la etiqueta cambie, que es el único caso con una fecha límite futura asociada. Una URL de CDN fijada y un pin exacto en npm nunca cambian por sí solos. Un rango de npm como ^2.0.0 se mantiene dentro de 2.x independientemente de los dist-tags. Para instalar 4.0 hoy, fíjalo: npm install htmx.org@4.0.0, o usa la ruta de CDN con versión.
Comienza los proyectos nuevos con 4.0. Para una aplicación existente con htmx 2, ejecuta el escáner, haz primero el renombrado de hx-disable y decide, según la longitud del informe, si migrar ahora o retomarlo antes de que se mueva el dist-tag.
Preguntas frecuentes
¿Cómo cargo una extensión de htmx en htmx 4 ahora que hx-ext se ha eliminado?
Incluye el script de la extensión después del script de htmx y sus atributos funcionan de inmediato, sin necesidad de un atributo de activación. Carga dist/ext/hx-sse.js junto a htmx.min.js y podrás usar hx-sse:connect directamente. La distribución htmax.js incluye htmx preempaquetado con las extensiones más populares en un solo archivo, con esos atributos disponibles automáticamente. Los autores de extensiones se registran mediante htmx.registerExtension con un nombre y un mapa de métodos.
¿Puedo evitar la petición adicional al servidor que htmx 4 realiza en la navegación hacia atrás?
Sí. La extensión del núcleo hx-history-cache restaura el historial desde sessionStorage en lugar de emitir una petición completa al servidor, que es el equivalente más cercano a los snapshots de htmx 2. Como alternativa, dos valores de configuración cambian el comportamiento: htmx.config.history establecido en 'reload' hace una recarga completa de página en la navegación del historial, y htmx.config.history establecido en false desactiva la gestión del historial. La caché de snapshots en localStorage de htmx 2 ha desaparecido.
¿Qué reemplaza a hx-vars y hx-prompt en htmx 4?
hx-vars se ha eliminado, y los valores calculados pasan a hx-vals con el prefijo js:. hx-prompt se elimina del núcleo y se distribuye como extensión: carga la extensión hx-prompt para mantener la misma sintaxis. Otros atributos eliminados son hx-ext, hx-inherit, hx-disinherit y hx-history. hx-disabled-elt se renombra en lugar de eliminarse: pasa a ser hx-disable, y el antiguo hx-disable pasa a ser hx-ignore, tal como establece la tabla de renombrados en [What's New in htmx 4](https://four.htmx.org/docs/whats-new-in-htmx-4). El escáner upgrade-check marca estos dos como renamed-attr y las eliminaciones reales como removed-attr, cada uno con el archivo, el número de línea y el reemplazo sugerido.
¿Sigue funcionando hx-swap-oob en htmx 4, y cuándo debería usar hx-partial en su lugar?
hx-swap-oob sigue funcionando, pero htmx 4 invierte el orden: el contenido principal entra primero, y los elementos out-of-band y hx-partial lo siguen en el orden del documento. Recurre a hx-swap-oob cuando estés reemplazando un elemento por una copia actualizada de ese mismo elemento, y a hx-partial cuando una única respuesta deba actualizar varios lugares, ya que cada partial declara su propio hx-target y hx-swap en vez de depender de atributos repartidos por el marcado.
Gain Debugging Superpowers
Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.
Star on GitHub12k