htmx: o retorno do HTML como linguagem de aplicação

1. O que é htmx e por que ele representa uma mudança de paradigma

htmx é uma biblioteca JavaScript que permite criar aplicações web dinâmicas usando HTML puro como linguagem de interação. Diferente das abordagens modernas que exigem frameworks pesados como React, Vue ou Angular, htmx resgata o princípio original da web: hipertexto como plataforma de aplicação.

A motivação central é simples: por que escrever dezenas de milhares de linhas de JavaScript para fazer algo que o HTML poderia fazer se estendêssemos seus atributos? htmx adiciona atributos como hx-get, hx-post e hx-target ao HTML, permitindo que qualquer elemento faça requisições AJAX, atualize partes da página e responda a eventos — tudo sem escrever uma única linha de JavaScript customizado.

Na lista de 1200 temas, htmx representa o minimalismo, a performance e a simplicidade. É uma ferramenta que abraça a filosofia de que o servidor deve ser o centro da lógica de aplicação, enquanto o cliente permanece leve e descartável.

2. Atributos principais: hx-get, hx-post, hx-target e hx-swap

Os quatro atributos fundamentais do htmx formam a base de qualquer interação:

<!-- Exemplo básico: carregar conteúdo ao clicar -->
<button hx-get="/api/conteudo" hx-target="#resultado" hx-swap="innerHTML">
  Carregar conteúdo
</button>

<div id="resultado">
  Conteúdo inicial aqui
</div>

hx-get define a URL para requisição GET. hx-target especifica qual elemento receberá a resposta. hx-swap controla como o conteúdo será inserido:

<!-- Diferentes modos de swap -->
<div hx-get="/dados" hx-target="#alvo" hx-swap="outerHTML">
  Substitui o elemento alvo completamente
</div>

<div hx-get="/dados" hx-target="#alvo" hx-swap="beforeend">
  Adiciona ao final do elemento alvo
</div>

<div hx-get="/dados" hx-target="#alvo" hx-swap="afterbegin">
  Adiciona ao início do elemento alvo
</div>

3. Interações avançadas com eventos e triggers

O atributo hx-trigger permite controlar precisamente quando as requisições acontecem:

<!-- Requisição ao digitar (com debounce de 500ms) -->
<input 
  type="text" 
  name="busca"
  hx-get="/buscar" 
  hx-trigger="keyup changed delay:500ms"
  hx-target="#resultados-busca"
  placeholder="Digite para buscar..."
/>

<div id="resultados-busca"></div>

<!-- Requisição quando o elemento fica visível (lazy loading) -->
<div hx-get="/carregar-mais" hx-trigger="revealed">
  Carregando mais conteúdo...
</div>

Eventos customizados podem ser combinados com o ciclo de vida do htmx:

<div 
  hx-get="/atualizar" 
  hx-trigger="customEvent from:body"
  hx-on::before-request="console.log('Iniciando requisição')"
  hx-on::after-request="console.log('Requisição concluída')"
>
  Elemento que responde a eventos customizados
</div>

4. Substituindo funcionalidades clássicas de JavaScript sem escrever JS

Validação de formulários e feedback inline tornam-se triviais:

<form hx-post="/cadastrar" hx-target="#mensagem">
  <input 
    type="email" 
    name="email" 
    required 
    hx-post="/validar-email" 
    hx-trigger="change" 
    hx-target="#email-feedback"
  />
  <div id="email-feedback"></div>

  <button type="submit">Cadastrar</button>
</form>

<div id="mensagem"></div>

Atualização parcial de páginas sem frameworks:

<!-- Lista de tarefas com exclusão inline -->
<ul id="tarefas">
  <li>
    Tarefa 1
    <button hx-delete="/tarefas/1" hx-target="#tarefas" hx-swap="outerHTML">
      Excluir
    </button>
  </li>
  <li>
    Tarefa 2
    <button hx-delete="/tarefas/2" hx-target="#tarefas" hx-swap="outerHTML">
      Excluir
    </button>
  </li>
</ul>

5. htmx no ecossistema de servidores: integração com qualquer backend

A beleza do htmx é que o servidor não precisa saber que está sendo usado. Basta retornar HTML puro:

<!-- Backend em Node/Express -->
app.get('/api/conteudo', (req, res) => {
  res.send(`
    <div class="card">
      <h3>Conteúdo carregado via htmx</h3>
      <p>Este HTML veio do servidor sem JSON ou APIs REST</p>
      <small>Carregado em: ${new Date().toLocaleTimeString()}</small>
    </div>
  `);
});

<!-- Backend em PHP -->
<?php
  if ($_SERVER['REQUEST_METHOD'] === 'GET' && $_GET['action'] === 'conteudo') {
    echo '<div class="card">';
    echo '  <h3>Conteúdo carregado via htmx</h3>';
    echo '  <p>PHP retornando HTML puro</p>';
    echo '</div>';
    exit;
  }
?>

6. Performance e acessibilidade como benefícios colaterais

Comparado a SPAs tradicionais, htmx oferece vantagens significativas:

  • Tamanho mínimo: htmx tem ~14KB minificado e gzipado, contra centenas de KB de frameworks
  • SEO nativo: HTML semântico é indexado por mecanismos de busca sem necessidade de SSR
  • Acessibilidade: funciona com leitores de tela e navegadores sem JavaScript (progressivo)
  • Menos latência: sem bundle splitting, code splitting ou hydration complexo
<!-- Funciona sem JavaScript (progressivo) -->
<noscript>
  <a href="/conteudo-completo">Ver conteúdo completo</a>
</noscript>

<button 
  hx-get="/conteudo-completo" 
  hx-target="#conteudo"
>
  Carregar conteúdo
</button>

7. Limitações, quando evitar htmx e alternativas

htmx não é adequado para:

  • Aplicações com estado complexo no cliente: jogos, editores de imagem, dashboards em tempo real com WebSockets
  • Interfaces que exigem manipulação DOM intensa e síncrona: animações complexas, drag-and-drop sofisticado
  • Aplicações offline-first: Progressive Web Apps que precisam funcionar sem servidor

Alternativas complementares:

  • Alpine.js: para interações leves no cliente que não justificam ida ao servidor
  • Stimulus: para complementar htmx com comportamentos modulares
  • Web Components: para encapsulamento de componentes reutilizáveis

8. Exemplo completo: uma aplicação CRUD funcional com htmx

Estrutura de arquivos:

crud-htmx/
├── index.html
├── server.js
└── dados.json

Frontend (index.html):

<!DOCTYPE html>
<html lang="pt-BR">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>CRUD com htmx</title>
  <script src="https://unpkg.com/htmx.org@1.9.10"></script>
</head>
<body>
  <h1>Gerenciador de Tarefas</h1>

  <form hx-post="/tarefas" hx-target="#lista-tarefas" hx-swap="beforeend">
    <input type="text" name="titulo" required placeholder="Nova tarefa">
    <button type="submit">Adicionar</button>
  </form>

  <div id="lista-tarefas">
    <!-- Tarefas serão carregadas aqui -->
  </div>

  <button hx-get="/tarefas" hx-target="#lista-tarefas" hx-swap="innerHTML">
    Carregar tarefas
  </button>
</body>
</html>

Backend (server.js - Node/Express):

const express = require('express');
const fs = require('fs');
const app = express();

app.use(express.urlencoded({ extended: true }));
app.use(express.static('.'));

// Listar tarefas
app.get('/tarefas', (req, res) => {
  const dados = JSON.parse(fs.readFileSync('dados.json'));
  let html = '';
  dados.tarefas.forEach(t => {
    html += `
      <div id="tarefa-${t.id}">
        <span>${t.titulo}</span>
        <button hx-delete="/tarefas/${t.id}" 
                hx-target="#tarefa-${t.id}" 
                hx-swap="outerHTML">
          Excluir
        </button>
      </div>
    `;
  });
  res.send(html);
});

// Criar tarefa
app.post('/tarefas', (req, res) => {
  const dados = JSON.parse(fs.readFileSync('dados.json'));
  const nova = { id: Date.now(), titulo: req.body.titulo };
  dados.tarefas.push(nova);
  fs.writeFileSync('dados.json', JSON.stringify(dados));

  res.send(`
    <div id="tarefa-${nova.id}">
      <span>${nova.titulo}</span>
      <button hx-delete="/tarefas/${nova.id}" 
              hx-target="#tarefa-${nova.id}" 
              hx-swap="outerHTML">
        Excluir
      </button>
    </div>
  `);
});

// Excluir tarefa
app.delete('/tarefas/:id', (req, res) => {
  const dados = JSON.parse(fs.readFileSync('dados.json'));
  dados.tarefas = dados.tarefas.filter(t => t.id != req.params.id);
  fs.writeFileSync('dados.json', JSON.stringify(dados));
  res.send(''); // Remove o elemento
});

app.listen(3000);

dados.json inicial:

{
  "tarefas": [
    { "id": 1, "titulo": "Aprender htmx" },
    { "id": 2, "titulo": "Construir um CRUD" }
  ]
}

Este exemplo demonstra como htmx permite criar uma aplicação completa com Create, Read, Update e Delete usando apenas HTML e um backend simples — sem JavaScript de framework, sem JSON, sem APIs REST complexas. Apenas HTML fluindo entre cliente e servidor, como a web sempre foi concebida para funcionar.

Referências