Voltar para o Blog

HTMX 2.0 Chega Com Revolucao: HTML Interativo Sem JavaScript Pesado

Olá HaWkers, o HTMX acaba de lançar a versão 2.0, consolidando-se como uma alternativa poderosa aos frameworks JavaScript tradicionais. Se você está cansado de bundles de 500KB+ e da complexidade de React, Vue ou Angular para aplicações simples, o HTMX pode ser exatamente o que você precisa.

Você já se perguntou se realmente precisa de toda essa complexidade para criar uma aplicação web interativa? Vamos explorar como o HTMX está mudando a forma como pensamos sobre desenvolvimento frontend.

O Que É HTMX

HTMX é uma biblioteca que permite acessar funcionalidades modernas do navegador diretamente do HTML, sem escrever JavaScript. Com apenas 14KB (gzipped), ela oferece:

  • AJAX requests via atributos HTML
  • CSS Transitions
  • WebSockets
  • Server-Sent Events
  • Histórico do navegador
  • Validação de formulários

Filosofia do HTMX

A ideia central é simples: o servidor retorna HTML, não JSON. Isso elimina a necessidade de:

  • Virtual DOM
  • State management complexo
  • Build tools elaboradas
  • Hidratação de componentes

💡 Conceito: HTMX estende o HTML para ser uma linguagem completa de hypermedia, como originalmente concebido.

Novidades do HTMX 2.0

A versão 2.0 traz melhorias significativas que tornam a biblioteca ainda mais poderosa:

Principais Features Novas

1. Enhanced Swap Modes:

  • morph - atualização inteligente preservando estado
  • multi - múltiplos targets em uma request
  • none - request sem swap (apenas side effects)

2. Improved Events:

  • htmx:beforeSwap melhorado com mais controle
  • htmx:afterSwap com informações de timing
  • Eventos customizáveis por elemento

3. Performance:

  • Parsing 50% mais rápido
  • Menor footprint de memória
  • Lazy loading aprimorado

4. Developer Experience:

  • Melhor debugging com htmx.logAll()
  • Extension API simplificada
  • TypeScript types incluídos

Primeiros Passos com HTMX 2.0

Vamos criar uma aplicação completa para demonstrar o poder do HTMX:

Instalação

<!-- Via CDN -->
<script src="https://unpkg.com/htmx.org@2.0.0"></script>

<!-- Ou via npm -->
<!-- npm install htmx.org -->

Estrutura HTML Básica

<!DOCTYPE html>
<html lang="pt-BR">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>App HTMX 2.0</title>
    <script src="https://unpkg.com/htmx.org@2.0.0"></script>
    <link href="https://cdn.jsdelivr.net/npm/tailwindcss@3/dist/tailwind.min.css" rel="stylesheet">
</head>
<body class="bg-gray-100 min-h-screen">
    <div class="container mx-auto px-4 py-8">
        <h1 class="text-3xl font-bold mb-8">Gerenciador de Tarefas</h1>

        <!-- Formulário de nova tarefa -->
        <form
            hx-post="/api/tasks"
            hx-target="#task-list"
            hx-swap="beforeend"
            hx-on::after-request="this.reset()"
            class="mb-8 flex gap-4"
        >
            <input
                type="text"
                name="title"
                placeholder="Nova tarefa..."
                required
                class="flex-1 px-4 py-2 rounded border focus:ring-2 focus:ring-blue-500"
            >
            <button
                type="submit"
                class="px-6 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
            >
                Adicionar
            </button>
        </form>

        <!-- Lista de tarefas -->
        <ul
            id="task-list"
            hx-get="/api/tasks"
            hx-trigger="load"
            hx-swap="innerHTML"
            class="space-y-2"
        >
            <!-- Tarefas carregadas aqui -->
        </ul>
    </div>
</body>
</html>

Backend com Express.js

O backend é simples porque retorna HTML, não JSON:

// server.js
const express = require('express');
const app = express();

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

// Database simulado
let tasks = [
    { id: 1, title: 'Aprender HTMX', completed: false },
    { id: 2, title: 'Criar projeto demo', completed: false },
];
let nextId = 3;

// Helper para gerar HTML de uma tarefa
function taskToHtml(task) {
    const completedClass = task.completed ? 'line-through text-gray-500' : '';
    const buttonText = task.completed ? 'Reabrir' : 'Concluir';
    const buttonColor = task.completed ? 'bg-yellow-500' : 'bg-green-500';

    return `
        <li id="task-${task.id}" class="flex items-center gap-4 p-4 bg-white rounded shadow">
            <span class="flex-1 ${completedClass}">${task.title}</span>

            <button
                hx-patch="/api/tasks/${task.id}/toggle"
                hx-target="#task-${task.id}"
                hx-swap="outerHTML"
                class="px-3 py-1 ${buttonColor} text-white rounded text-sm"
            >
                ${buttonText}
            </button>

            <button
                hx-delete="/api/tasks/${task.id}"
                hx-target="#task-${task.id}"
                hx-swap="outerHTML swap:1s"
                hx-confirm="Tem certeza que deseja excluir?"
                class="px-3 py-1 bg-red-500 text-white rounded text-sm"
            >
                Excluir
            </button>
        </li>
    `;
}

// GET /api/tasks - Listar todas as tarefas
app.get('/api/tasks', (req, res) => {
    const html = tasks.map(taskToHtml).join('');
    res.send(html);
});

// POST /api/tasks - Criar nova tarefa
app.post('/api/tasks', (req, res) => {
    const { title } = req.body;

    if (!title || title.trim() === '') {
        return res.status(400).send('<p class="text-red-500">Título é obrigatório</p>');
    }

    const task = {
        id: nextId++,
        title: title.trim(),
        completed: false,
    };

    tasks.push(task);
    res.send(taskToHtml(task));
});

// PATCH /api/tasks/:id/toggle - Alternar status
app.patch('/api/tasks/:id/toggle', (req, res) => {
    const id = parseInt(req.params.id);
    const task = tasks.find(t => t.id === id);

    if (!task) {
        return res.status(404).send('<p class="text-red-500">Tarefa não encontrada</p>');
    }

    task.completed = !task.completed;
    res.send(taskToHtml(task));
});

// DELETE /api/tasks/:id - Excluir tarefa
app.delete('/api/tasks/:id', (req, res) => {
    const id = parseInt(req.params.id);
    const index = tasks.findIndex(t => t.id === id);

    if (index === -1) {
        return res.status(404).send('');
    }

    tasks.splice(index, 1);
    res.send(''); // Elemento será removido do DOM
});

app.listen(3000, () => {
    console.log('Server running on http://localhost:3000');
});

Features Avançadas do HTMX 2.0

Infinite Scroll

Implemente scroll infinito com apenas atributos HTML:

<div id="posts-container">
    <!-- Posts existentes -->
    <article class="post">...</article>
    <article class="post">...</article>

    <!-- Trigger para carregar mais -->
    <div
        hx-get="/api/posts?page=2"
        hx-trigger="revealed"
        hx-swap="outerHTML"
        class="loading-indicator"
    >
        <span class="animate-spin">⏳</span> Carregando mais...
    </div>
</div>
// Backend retorna próximos posts + novo trigger
app.get('/api/posts', (req, res) => {
    const page = parseInt(req.query.page) || 1;
    const posts = getPostsForPage(page);
    const hasMore = checkIfHasMorePosts(page);

    let html = posts.map(post => `
        <article class="post">
            <h2>${post.title}</h2>
            <p>${post.excerpt}</p>
        </article>
    `).join('');

    if (hasMore) {
        html += `
            <div
                hx-get="/api/posts?page=${page + 1}"
                hx-trigger="revealed"
                hx-swap="outerHTML"
                class="loading-indicator"
            >
                <span class="animate-spin">⏳</span> Carregando mais...
            </div>
        `;
    }

    res.send(html);
});

Search com Debounce

Busca em tempo real com debounce automático:

<input
    type="search"
    name="q"
    placeholder="Buscar..."
    hx-get="/api/search"
    hx-trigger="input changed delay:300ms"
    hx-target="#search-results"
    hx-indicator="#search-spinner"
    class="w-full px-4 py-2 border rounded"
>

<span id="search-spinner" class="htmx-indicator">
    🔄 Buscando...
</span>

<div id="search-results">
    <!-- Resultados aparecem aqui -->
</div>
app.get('/api/search', (req, res) => {
    const query = req.query.q || '';

    if (query.length < 2) {
        return res.send('<p class="text-gray-500">Digite pelo menos 2 caracteres</p>');
    }

    const results = searchDatabase(query);

    if (results.length === 0) {
        return res.send('<p class="text-gray-500">Nenhum resultado encontrado</p>');
    }

    const html = results.map(item => `
        <div class="p-2 hover:bg-gray-100 cursor-pointer" onclick="selectItem(${item.id})">
            <strong>${highlightMatch(item.title, query)}</strong>
            <p class="text-sm text-gray-600">${item.description}</p>
        </div>
    `).join('');

    res.send(html);
});

Validação de Formulário em Tempo Real

<form hx-post="/api/register" hx-target="#form-result">
    <div class="mb-4">
        <label class="block mb-1">Email</label>
        <input
            type="email"
            name="email"
            hx-get="/api/validate/email"
            hx-trigger="blur changed"
            hx-target="next .validation-message"
            class="w-full px-4 py-2 border rounded"
        >
        <span class="validation-message text-sm"></span>
    </div>

    <div class="mb-4">
        <label class="block mb-1">Username</label>
        <input
            type="text"
            name="username"
            hx-get="/api/validate/username"
            hx-trigger="input changed delay:500ms"
            hx-target="next .validation-message"
            class="w-full px-4 py-2 border rounded"
        >
        <span class="validation-message text-sm"></span>
    </div>

    <button type="submit" class="px-6 py-2 bg-blue-600 text-white rounded">
        Registrar
    </button>
</form>

<div id="form-result"></div>
app.get('/api/validate/email', (req, res) => {
    const email = req.query.email;
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;

    if (!emailRegex.test(email)) {
        return res.send('<span class="text-red-500">Email inválido</span>');
    }

    const exists = checkEmailExists(email);
    if (exists) {
        return res.send('<span class="text-red-500">Email já cadastrado</span>');
    }

    res.send('<span class="text-green-500">✓ Email disponível</span>');
});

app.get('/api/validate/username', (req, res) => {
    const username = req.query.username;

    if (username.length < 3) {
        return res.send('<span class="text-red-500">Mínimo 3 caracteres</span>');
    }

    if (!/^[a-zA-Z0-9_]+$/.test(username)) {
        return res.send('<span class="text-red-500">Apenas letras, números e _</span>');
    }

    const exists = checkUsernameExists(username);
    if (exists) {
        return res.send('<span class="text-red-500">Username já em uso</span>');
    }

    res.send('<span class="text-green-500">✓ Username disponível</span>');
});

HTMX vs React: Quando Usar Cada Um

Aspecto HTMX React
Tamanho 14KB 40KB+ (sem deps)
Learning curve Baixa Alta
State management Servidor Cliente
Build tools Opcional Obrigatório
SEO Excelente Requer SSR
Interatividade offline Limitada Excelente
Componentes reutilizáveis Básico Avançado
Ecossistema Crescendo Enorme

Use HTMX Quando

  • Aplicações CRUD tradicionais
  • Dashboards e admin panels
  • Sites com SEO importante
  • Equipes pequenas ou solo
  • Projetos com backend forte
  • Prototipagem rápida

Use React Quando

  • SPAs complexas
  • Aplicações offline-first
  • Muita interatividade client-side
  • Equipes grandes com especialistas frontend
  • PWAs avançados
  • Componentes altamente reutilizáveis

Integrações Populares

HTMX + Alpine.js

Para quando precisa de um pouco de JavaScript client-side:

<div x-data="{ open: false }">
    <button @click="open = !open" class="px-4 py-2 bg-blue-600 text-white rounded">
        Toggle Menu
    </button>

    <div x-show="open" x-transition>
        <nav
            hx-get="/api/menu"
            hx-trigger="load"
            class="mt-2 p-4 bg-white shadow rounded"
        >
            <!-- Menu carregado via HTMX -->
        </nav>
    </div>
</div>

HTMX + Tailwind CSS

A combinação perfeita para desenvolvimento rápido sem build tools.

Conclusão

O HTMX 2.0 representa uma mudança de paradigma no desenvolvimento frontend. Ao trazer a lógica de volta para o servidor e usar HTML como formato de transferência, ele simplifica drasticamente aplicações web que não precisam de toda a complexidade de uma SPA.

Para muitos projetos, especialmente aqueles focados em produtividade e manutenibilidade, HTMX pode ser a escolha certa. Não é sobre abandonar JavaScript, mas sobre usá-lo onde realmente faz sentido.

Se você se sente inspirado a explorar alternativas ao JavaScript tradicional, recomendo que dê uma olhada em outro artigo: Tailwind CSS 4.0: O Que Há de Novo no Framework CSS Mais Popular onde você vai descobrir as novidades do Tailwind.

Bora pra cima! 🦅

Comentários (0)

Esse artigo ainda não possui comentários 😢. Seja o primeiro! 🚀🦅

Adicionar comentário