htmx 4.0 est disponible
htmx 4.0 modifie lhéritage, le swap des erreurs, lhistorique et les événements, avec des conseils de migration et des options de retour pour htmx 2.
htmx 4.0.0 est sorti le 28 août 2026. Cette version modifie plusieurs comportements par défaut installés de longue date : l’héritage d’attributs devient opt-in, les réponses d’erreur sont injectées dans le DOM, et le cache d’instantanés de l’historique disparaît.
Si vous maintenez une application htmx 2, la question concrète est de savoir si le hx-confirm que vous aviez remonté sur un conteneur il y a deux ans protège encore quoi que ce soit après la mise à niveau. Ce n’est pas le cas, sauf si vous ajoutez un modificateur. Cet article détaille ce qui casse, ce qui permet de revenir en arrière dans chaque cas, et pourquoi la stratégie de publication npm fait que vous n’avez probablement pas besoin d’agir cette semaine. Pour savoir ce qu’est htmx et pourquoi l’hypermédia, la présentation de htmx 2.0 couvre les bases dont part cet article.
Points clés
- htmx 4 rend l’héritage d’attributs explicite via le modificateur
:inherited, et définirhtmx.config.implicitInheritancesurtruerétablit le comportement de htmx 2 comme passerelle de migration. - Seules les réponses
204et304échappent au swap par défaut dans htmx 4 : une réponse422rendue côté serveur atterrit désormais dans la cible au lieu d’être ignorée ;htmx.config.noSwap = [204, 304, '4xx', '5xx']rétablit l’ancien comportement. - Les noms d’événements suivent le schéma
htmx:phase:action, sans clé de configuration associée : chaque écouteur htmx de votre JavaScript doit être renommé, à moins d’installer l’extensionhtmx-2-compat. - Renommez
hx-disableenhx-ignoreavant la mise à niveau, car htmx 4 réattribue le nomhx-disableau rôle qu’assuraithx-disabled-elt. - htmx 2.x conserve le tag npm
latesttandis que la 4.0 se trouve sousnext: les URL CDN sans numéro de version ne sont donc pas mises à niveau de force, et l’annonce précise que htmx 2 sera pris en charge indéfiniment.
Qu’est-ce qui change dans htmx 4 ?
htmx 4 fait passer les rouages internes des requêtes de XMLHttpRequest à fetch(), et c’est cette réécriture qui a rendu le reste de la version possible. Changer de couche de transport constituait déjà une rupture de compatibilité : l’équipe a donc profité de la même version majeure pour réinitialiser les valeurs par défaut accumulées depuis htmx 1.
Vous n’appelez directement ni l’une ni l’autre de ces API quand vous écrivez du htmx, si bien que le changement de transport reste invisible dans vos templates. Les conséquences se manifestent en périphérie : les événements de cycle de vie propres à XHR n’ont pas d’équivalent fetch() et ont été supprimés, et htmx 4 fixe htmx.config.defaultTimeout à 60000, là où htmx 2 laissait une requête pendre indéfiniment.
À propos du numéro de version : le créateur de htmx, Carson Gross, avait déclaré qu’il n’y aurait jamais de htmx 3 ; la version saute donc directement à 4.0 et la promesse survit sur un détail technique. Il en a exposé le raisonnement dans l’essai qui annonçait la réécriture en novembre 2025.
L’héritage d’attributs est désormais explicite
Dans htmx 4, l’héritage n’a lieu que si vous le demandez. Un attribut posé sur un conteneur ne s’applique qu’à ce conteneur, sauf si vous ajoutez le modificateur :inherited, lequel fonctionne avec n’importe quel attribut : 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>
Par défaut, une valeur définie sur un enfant l’emporte sur la valeur héritée. Utilisez :append lorsque vous voulez combiner les deux — c’est le cas de composition qui piège tout le monde :
<div hx-vals:inherited="tenant:acme">
<button hx-post="/save" hx-vals:append="source:save-btn">Save</button>
</div>
Sans :append, le hx-vals propre au bouton se substitue à la valeur héritée et tenant n’atteint jamais le serveur. Si aucun ancêtre ne définit l’attribut, la valeur ajoutée est la seule envoyée. Les noms varient légèrement selon l’attribut : la page de référence de hx-disable documente :merge pour le même rôle d’ajout à la valeur d’un parent.
hx-inherit et hx-disinherit sont supprimés, l’opt-in explicite rendant l’un et l’autre inutiles. Si vos templates reposent sur l’ancien comportement, définissez htmx.config.implicitInheritance sur true pour le rétablir le temps de votre migration. Considérez cela comme une passerelle, pas comme une destination.
Les réponses d’erreur déclenchent un swap par défaut
Dans htmx 4, une réponse atteint la cible quel que soit son code de statut, seuls 204 et 304 étant retenus. Une page de validation 422 rendue côté serveur atterrit désormais dans la cible au lieu d’être silencieusement écartée — ce que les applications hypermédia attendaient depuis le début. Une réponse d’erreur HTTP déclenche en outre un événement htmx:response:error.
Le nouvel attribut hx-status aiguille chaque code vers sa propre cible et son propre 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 essaie d’abord le motif le plus spécifique : le code exact, puis un motif dont le dernier chiffre est masqué (par exemple 50x), puis un motif dont les deux derniers le sont (par exemple 5xx). Dans la valeur de l’attribut, vous pouvez définir swap:, target:, select:, push:, replace: et transition:.
Si votre backend renvoie des pages d’erreur qui n’ont jamais été conçues pour être injectées, définissez htmx.config.noSwap sur [204, 304, '4xx', '5xx'] et vous retrouvez le comportement de htmx 2.
La navigation arrière déclenche désormais une vraie requête
htmx 4 abandonne le cache d’instantanés du DOM côté client sur lequel reposait l’historique dans htmx 2. Appuyez sur « Précédent » et htmx redemande la page au serveur, puis injecte ce qui revient dans <body>, ou dans un élément [hx-history-elt] si la page en comporte un.
Concrètement, le bouton Précédent affiche l’état actuel de la page selon le serveur, et non un instantané figé au moment où vous avez quitté la page. Cela élimine toute une catégorie de bugs où des scripts tiers modifiaient le DOM et où l’instantané restauré rejouait ces modifications vers un état corrompu. En contrepartie, la navigation arrière coûte une requête.
L’attribut hx-history disparaît avec le cache. Si vous avez besoin d’instantanés, l’extension de base hx-history-cache les réintroduit en opt-in.
Les noms d’événements suivent le schéma htmx:phase:action
Tous les événements de cycle de vie de htmx ont été renommés selon la forme htmx:phase:action[:sub-action]. L’annonce donne htmx:beforeRequest devenu htmx:before:request et htmx:beforeSwap devenu htmx:before:swap ; htmx:afterSwap devient htmx:after:swap.
C’est le seul changement sans échappatoire par configuration. Chaque écouteur doit être modifié :
// 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 plupart des événements d’erreur sont regroupés sous htmx:error, les réponses d’erreur HTTP déclenchant htmx:response:error. Les événements propres à XHR ont purement et simplement disparu, fetch() n’exposant aucun équivalent. Si la réécriture manuelle des écouteurs représente l’essentiel de votre migration, l’extension htmx-2-compat mappe les anciens noms d’événements sur les nouveaux et rétablit également l’héritage implicite ainsi que hx-ext.
Qu’y a-t-il de neuf dans htmx 4, plutôt que de cassé ?
Trois ajouts de htmx 4 justifient à eux seuls la mise à niveau : les swaps par morphing, l’élément <hx-partial> et les extensions de streaming réécrites. Les swaps par morphing sont intégrés au cœur de la bibliothèque : les mises à jour du DOM préservant l’état n’exigent donc plus d’extension. L’élément <hx-partial> permet à une même réponse de mettre à jour plusieurs cibles, chacune portant sa cible et son 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>
Comme la cible et le mode de swap figurent sur le fragment lui-même, la réponse indique ce qu’elle veut qu’on fasse de chaque morceau, au lieu de vous laisser le déduire d’attributs hx-swap-oob éparpillés dans le balisage. Notez que l’ordre des swaps out-of-band s’est inversé dans htmx 4 : le contenu principal est injecté en premier.
Les extensions de streaming constituent l’autre grande nouveauté. Les extensions SSE et WebSocket ont toutes deux été reconstruites pour cette version, et un nouvel ensemble les accompagne : hx-multipart, hx-live, hx-targets, hx-ptag, hx-csp, hx-download, hx-prompt et hx-history-cache. Les attributs de connexion sont préfixés par un espace de noms : SSE se connecte avec hx-sse:connect et les WebSockets avec hx-ws:connect.
Migrer vers htmx 4, et pourquoi rien ne presse
La migration vers htmx 4 commence par le scanner : lancez-le avant de planifier quoi que ce soit. npx htmx.org@4.0.0 upgrade-check -- ./path/to/project/root parcourt votre projet et affiche chaque motif déprécié avec son fichier et son numéro de ligne, ce qui suffit à évaluer l’ampleur du chantier en un après-midi.
npx htmx.org@4.0.0 upgrade-check -- ./templates
npx htmx.org@4.0.0 upgrade-check --ext .vue ./path/to/project/root
Par défaut, il examine les fichiers .html, .php, .js, .ts, .jinja, .jinja2, .j2, .erb et .hbs. Les formats de composants monofichiers n’en font pas partie : les templates .vue, .svelte, .jsx et .astro ne sont donc pas analysés, sauf si vous passez --ext.
Effectuez un renommage avant toute autre chose : hx-disable devient hx-ignore, et hx-disabled-elt devient hx-disable. L’ancien nom est réutilisé pour un autre rôle : si vous migrez hx-disabled-elt en premier, vous écraserez des attributs qui ont encore le sens qu’ils avaient dans htmx 2.
| Changement | Valeur par défaut htmx 4 | Comment retrouver htmx 2 |
|---|---|---|
| Héritage d’attributs | Explicite, via :inherited | htmx.config.implicitInheritance = true |
| Swap des réponses d’erreur | Seuls 204/304 échappent au swap | htmx.config.noSwap = [204, 304, '4xx', '5xx'] |
| Historique | Nouvelle requête serveur au retour arrière | Extension hx-history-cache |
| Noms d’événements | htmx:phase:action | Aucune clé de configuration ; extension htmx-2-compat |
Vient ensuite l’élément qui détermine l’urgence de tout cela : sur npm, htmx 2.x détient le dist-tag latest et la 4.0.0 est publiée sous next. L’annonce précise explicitement que c’est délibéré, afin que les sites chargeant htmx depuis une URL CDN sans numéro de version ne subissent pas de mise à niveau forcée vers des changements cassants, la 2.x conservant le tag latest jusqu’au début 2027. La 2.x reste prise en charge indéfiniment.
Cela se traduit par quatre situations. Une URL CDN sans version continue de servir la 2.x jusqu’à ce que le tag bascule : c’est le seul cas assorti d’une échéance. Une URL CDN figée sur une version et un pin npm exact ne changent jamais d’eux-mêmes. Une plage npm du type ^2.0.0 reste dans la 2.x, quels que soient les dist-tags. Pour installer la 4.0 dès aujourd’hui, épinglez-la : npm install htmx.org@4.0.0, ou utilisez le chemin CDN versionné.
Démarrez les nouveaux projets sur la 4.0. Pour une application htmx 2 existante, lancez le scanner, effectuez d’abord le renommage de hx-disable, puis décidez, au vu de la longueur du rapport, s’il faut migrer maintenant ou y revenir avant que le dist-tag ne bascule.
FAQ
Comment charger une extension htmx dans htmx 4 maintenant que hx-ext est supprimé ?
Incluez le script de l'extension après celui de htmx et ses attributs fonctionnent immédiatement, sans attribut d'activation. Chargez dist/ext/hx-sse.js aux côtés de htmx.min.js et vous pouvez utiliser hx-sse:connect directement. La distribution htmax.js fournit htmx pré-empaqueté avec les extensions les plus populaires dans un seul fichier, ces attributs étant automatiquement disponibles. Les auteurs d'extensions s'enregistrent via htmx.registerExtension avec un nom et une table de méthodes.
Puis-je éviter la requête serveur supplémentaire que htmx 4 effectue lors de la navigation arrière ?
Oui. L'extension de base hx-history-cache restaure l'historique depuis sessionStorage plutôt que d'émettre une requête serveur complète, ce qui constitue l'équivalent le plus proche des instantanés de htmx 2. Deux valeurs de configuration modifient aussi ce comportement : htmx.config.history défini sur 'reload' effectue un rechargement complet de la page lors de la navigation dans l'historique, et htmx.config.history défini sur false désactive la gestion de l'historique. Le cache d'instantanés localStorage de htmx 2 a disparu.
Par quoi hx-vars et hx-prompt sont-ils remplacés dans htmx 4 ?
hx-vars est supprimé, et les valeurs calculées passent dans hx-vals avec le préfixe js:. hx-prompt est retiré du cœur et distribué en tant qu'extension : chargez l'extension hx-prompt pour conserver la même syntaxe. Parmi les autres attributs supprimés figurent hx-ext, hx-inherit, hx-disinherit et hx-history. hx-disabled-elt est renommé plutôt que supprimé : il devient hx-disable, et l'ancien hx-disable devient hx-ignore, comme l'indique le tableau des renommages dans [Nouveautés de htmx 4](https://four.htmx.org/docs/whats-new-in-htmx-4). Le scanner upgrade-check signale ces deux cas comme renamed-attr et les véritables suppressions comme removed-attr, chacun accompagné du fichier, du numéro de ligne et du remplacement suggéré.
hx-swap-oob fonctionne-t-il toujours dans htmx 4, et quand faut-il lui préférer hx-partial ?
hx-swap-oob fonctionne toujours, mais htmx 4 inverse l'ordre : le contenu principal est injecté en premier, et les éléments out-of-band ainsi que hx-partial suivent dans l'ordre du document. Optez pour hx-swap-oob lorsque vous remplacez un élément par une copie mise à jour du même élément, et pour hx-partial lorsqu'une seule réponse doit mettre à jour plusieurs endroits, puisque chaque fragment déclare ses propres hx-target et hx-swap au lieu de dépendre d'attributs disséminés dans le balisage.
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