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 kbGzippé. Pas de build step.
0Dépendance. Un seul <script>.
HTMLAttributs 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


EffetB · 2026