EffetB · Daily Dev · 6 juillet 2026 · ~8 min de lecture
v2.0 14 kb gzippé 0 dépendances
Comment on en est arrivé là ?
Le développement web front a traversé trois grandes époques :
- Avant — HTML + PHP. Un lien, un rechargement de page. Simple, direct. Ça marchait.
- Ensuite — jQuery + AJAX. On évite les rechargements. Utile — mais on commence à gérer l'état manuellement.
- Aujourd'hui — React / Vue / Angular. Routeur, state manager, build step, hydration, SSR, bundle... pour afficher une liste.
Résultat : pour un simple bouton « Charger plus », on installe parfois 400 dépendances. Est-ce vraiment nécessaire ?
HTMX — c'est quoi ?
Quelques chiffres qui plantent le décor :
| 14 kb | Gzippé. Pas de build step. |
| 0 | Dépendance. Un seul <script>. |
| HTML | Attributs natifs. Zéro JS à écrire. |
HTMX étend le HTML pour lui donner des super-pouvoirs AJAX. N'importe quel élément peut déclencher une requête HTTP et mettre à jour une partie de la page — sans écrire une seule ligne de JavaScript. Le serveur renvoie du HTML, pas du JSON.
5 attributs suffisent
L'API tient sur une carte de visite :
hx-get="/url"— Déclenche une requête GET et injecte la réponse HTML dans la cible.hx-post="/url"— Requête POST — idéal pour les formulaires, actions avec effets de bord.hx-trigger="..."— Quand déclencher :click,change,submit,mouseenter,every 1s...hx-target="#id"— Sélecteur CSS de l'élément qui reçoit le fragment HTML de réponse.hx-swap="..."— Mode d'injection :innerHTML,outerHTML,beforeend,afterbegin,delete...
C'est tout. Pas de nouveau langage à apprendre, pas de compilateur, pas de virtual DOM.
Avant / après : charger des données au clic
Avant — JavaScript pur
<!-- HTML --> <button id="btn">Charger</button> <div id="result"></div>
// JavaScript
document
.getElementById('btn')
.addEventListener('click', async () => {
const res = await fetch('/api/data');
const html = await res.text();
document
.getElementById('result')
.innerHTML = html;
});Avec HTMX
<button hx-get="/api/data" hx-target="#result"> Charger </button> <div id="result"></div>
Zéro JavaScript. Même résultat. Même comportement.
Côté serveur : ce que PHP renvoie
Point important : HTMX attend du HTML en réponse — pas du JSON.
<?php
// /api/data.php
// C'est tout ce que le serveur fait.
header('Content-Type: text/html; charset=utf-8');
$items = $pdo->query('SELECT * FROM items')
->fetchAll();
foreach ($items as $item) {
$name = htmlspecialchars($item['name']);
echo "<li class='item'>{$name}</li>\n";
}
// Pas de json_encode.
// Pas de transformation côté client.
// Juste du HTML.Le serveur renvoie directement un fragment HTML prêt à être injecté dans la page. Aucun templating JavaScript, aucune désérialisation, aucun state à synchroniser entre client et serveur.
C'est exactement ce que PHP sait faire depuis 1994. HTMX ne fait qu'exploiter enfin cette force — compatible Laravel, Symfony, ou PHP natif, sans lib supplémentaire côté serveur.
Démo : barre de progression avec polling automatique
Un cas très courant : lancer un traitement long et afficher sa progression, sans une ligne de JavaScript personnalisée.
Twig — lancer le job
<button hx-post="/job/start" hx-target="#progress"> Lancer </button> <div id="progress"></div>
Symfony — contrôleur
#[Route('/job/start', methods: ['POST'])]
public function start(JobService $job): Response
{
$job->start();
return $this->render('job/_progress.html.twig');
}
#[Route('/job/status')]
public function status(JobService $job): Response
{
$percent = $job->getProgress();
if ($percent >= 100) {
// pas de hx-trigger → polling stoppé
return $this->render('job/_done.html.twig');
}
return $this->render('job/_progress.html.twig', [
'percent' => $percent,
]);
}Le fragment _progress.html.twig contient lui-même un hx-trigger="every 1s" qui relance l'appel vers /job/status — tant que le serveur renvoie ce fragment, le polling continue. Dès que le traitement est terminé, le serveur renvoie le fragment _done.html.twig, dépourvu de hx-trigger : le polling s'arrête naturellement, côté serveur, sans setInterval ni clearInterval à gérer en JS.
Démo : chargement infini au scroll
Autre classique : un tableau avec pagination façon « scroll infini », déclenché quand une ligne sentinelle entre dans le viewport.
Twig — tableau + sentinelle
<tbody id="rows">
{% for c in clients %}
<tr><td>{{ c.name }}</td></tr>
{% endfor %}
{% if hasMore %}
<tr hx-get="/clients?page={{ next }}"
hx-trigger="revealed"
hx-swap="outerHTML">
<td>Chargement...</td>
</tr>
{% endif %}
</tbody>Symfony — contrôleur
#[Route('/clients')]
public function list(
Request $request,
ClientRepository $repo
): Response {
$page = $request->query->getInt('page', 1);
return $this->render('client/_rows.html.twig', [
'clients' => $repo->paginate($page, 20),
'next' => $page + 1,
// false à la dernière page → hx-trigger
// absent, le scroll infini s'arrête
'hasMore' => $repo->hasMore($page),
]);
}hx-trigger="revealed" s'appuie sur un IntersectionObserver en interne : dès que la ligne-sentinelle devient visible, HTMX déclenche le hx-get qui remplace cette ligne (hx-swap="outerHTML") par la page suivante de résultats, elle-même terminée par une nouvelle sentinelle — ou non, si hasMore est false.
Besoin de JS ? On peut toujours
HTMX couvre l'essentiel sans JS — mais rien n'empêche d'en ajouter pour les cas particuliers (notification, animation, tracking...). HTMX émet des événements standards à chaque étape d'une requête, écoutables comme n'importe quel événement DOM.
HTML — inchangé
<button hx-get="/factures/refresh" hx-target="#list"> Mettre à jour </button> <ul id="list">...</ul>
JavaScript — écouter un événement HTMX
document.body.addEventListener(
'htmx:afterSwap',
(e) => {
showToast('Mis à jour !');
trackAnalytics('list_refreshed');
}
);De quoi aller plus loin sans tout réécrire — notifications, animations, tracking — tout en gardant HTMX comme fondation.
Quand adopter HTMX ?
Idéal pour :
- Applications PHP, Laravel, Symfony
- Tableaux avec filtres et pagination
- Formulaires avec validation inline
- Chargement différé de contenus
- Polling — statut de jobs, notifications
- Projets à interactivité modérée
Moins adapté pour :
- Interfaces très riches (type Figma, Notion)
- Applications 100% offline-first
- Un SPA déjà fonctionnel et stable
- Un state partagé très complexe côté client
Stack recommandée : PHP + HTMX + Alpine.js (pour les petites interactions JS) + Tailwind CSS — courbe d'apprentissage de 30 minutes, zéro build step, déploiement comme du PHP classique.
Pour aller plus loin
- Documentation complète, exemples interactifs et playground : htmx.org
- À lire aussi : la réponse de HTMX à Rich Harris sur pourquoi les SPA ne sont pas toujours la bonne réponse.
EffetB · 2026