Instalación y primer proyecto con htmx 4

Por: Artiko
htmxhtmx4instalacionconfiguracionhcon

Instalación y primer proyecto

htmx es un único archivo JavaScript sin dependencias. No hay bundler, no hay paso de compilación, no hay node_modules obligatorio.

Opción 1: CDN

La forma más rápida. Un <script> en el <head>:

<script src="https://cdn.jsdelivr.net/npm/[email protected]"
        integrity="sha384-6lyVbhrs13b9z7mLOpt/N6R76rtkEBWgCjAXRs/DSWyi2AMnQSs10ijWk+PI8n7W"
        crossorigin="anonymous"></script>

El atributo integrity es un hash SRI: el navegador rechaza el script si el contenido no coincide. Está atado a esa versión exacta, así que si cambias de versión tienes que cambiar el hash.

Producción: no dependas de un CDN de terceros para código crítico. Fija la versión, usa SRI y, mejor todavía, sírvelo desde tu propio dominio (opción 3).

Opción 2: npm

npm install [email protected]
# o con bun
bun add [email protected]

Y en tu bundle:

import 'htmx.org';

htmx se auto-inicializa al importarse: registra el listener del DOM y procesa el documento. Si necesitas la referencia global (por ejemplo para configurarlo), está en window.htmx.

Opción 3: self-hosted

  1. Descarga https://cdn.jsdelivr.net/npm/[email protected]/dist/htmx.min.js.
  2. Guárdalo en public/js/htmx.min.js.
  3. Inclúyelo:
<script src="/js/htmx.min.js"></script>

Es la opción con menos superficie de ataque y sin latencia de terceros. Para proyectos serios, es la recomendada.

flowchart TD
    A[¿Cómo instalo htmx?] --> B{¿Tienes bundler?}
    B -->|Sí| C["npm install [email protected]"]
    B -->|No| D{¿Es producción?}
    D -->|Prototipo| E["CDN con integrity"]
    D -->|Producción| F["Self-hosted en /js/"]

El primer proyecto

Vamos a montar un proyecto mínimo que ya hace una petición real. Dos archivos.

index.html

<!doctype html>
<html lang="es">
<head>
  <meta charset="utf-8">
  <title>htmx 4 — hola mundo</title>
  <script src="https://cdn.jsdelivr.net/npm/[email protected]"></script>
</head>
<body>
  <h1>Mi primera app con htmx 4</h1>

  <button hx-get="/hora" hx-target="#salida">
    ¿Qué hora es?
  </button>

  <div id="salida"></div>
</body>
</html>

servidor.js

const html = (contenido) =>
  new Response(contenido, { headers: { "Content-Type": "text/html; charset=utf-8" } });

Bun.serve({
  port: 3000,
  fetch(req) {
    const { pathname } = new URL(req.url);

    if (pathname === "/hora") {
      const ahora = new Date().toLocaleTimeString("es-CL");
      return html(`<p>Son las <strong>${ahora}</strong></p>`);
    }

    return new Response(Bun.file("./index.html"));
  },
});

console.log("http://localhost:3000");
bun run servidor.js

Abre http://localhost:3000, pulsa el botón y el <div id="salida"> se llena con el HTML que devolvió el servidor. Sin JSON, sin plantillas en el cliente, sin estado.

Qué pasó exactamente

sequenceDiagram
    participant B as Botón
    participant H as htmx
    participant S as Servidor
    participant D as #salida
    B->>H: clic (trigger por defecto)
    H->>S: GET /hora<br/>HX-Request: true<br/>HX-Target: div#salida
    S-->>H: 200 <p>Son las...</p>
    H->>D: innerHTML (swap por defecto)

Los valores por defecto que se aplicaron sin que los escribieras:

AspectoValor por defectoAtributo que lo cambia
Evento que disparaclick (en un <button>)hx-trigger
Destinoel propio elementohx-target
InsercióninnerHTMLhx-swap
Timeout60 000 mshtmx.config.defaultTimeout

Ojo con el timeout: en htmx 2 el valor por defecto era 0 (sin límite). En htmx 4 es 60 segundos.

Configuración global

htmx se configura de tres maneras. Todas escriben sobre el mismo objeto htmx.config.

1. Etiqueta <meta> con HCON

<meta name="htmx-config" content="defaultSwap:outerHTML transitions:true">

2. Etiqueta <meta> con JSON

<meta name="htmx-config" content='{"defaultSwap":"outerHTML","transitions":true}'>

3. JavaScript

<script src="/js/htmx.min.js"></script>
<script>
  htmx.config.defaultTimeout = 10000;
  htmx.config.defaultSwap = "outerHTML";
  htmx.config.transitions = true;
</script>

El <meta> debe ir antes del script de htmx para que se aplique durante la inicialización.

HCON: la notación de configuración de htmx

HCON (htmx Configuration Object Notation) es una sintaxis compacta de pares clave:valor separados por espacios o comas. Aparece en todo htmx 4, no sólo en la configuración global.

<!-- HCON -->
<meta name="htmx-config" content="defaultSwap:outerHTML transitions:true">

<!-- JSON equivalente -->
<meta name="htmx-config" content='{"defaultSwap":"outerHTML","transitions":true}'>

Reglas:

<!-- Una clave sola equivale a true -->
<button hx-config="validate">          <!-- validate:true -->

<!-- Booleano explícito -->
<button hx-config="validate:false">

<!-- Número -->
<button hx-config="timeout:5000">

<!-- String: entre comillas -->
<button hx-config='credentials:"include"'>

<!-- Claves anidadas con punto -->
<meta name="htmx-config" content="sse.reconnect:true sse.reconnectDelay:1000">
<!-- Resultado: {sse: {reconnect: true, reconnectDelay: 1000}} -->

Dónde se usa HCON:

  • Modificadores de hx-swap: innerHTML swap:200ms settle:100ms
  • Modificadores de hx-trigger: click delay:500ms throttle:1s
  • hx-config, hx-vals, hx-headers
  • <meta name="htmx-config">
  • El header de respuesta HX-Location

Si un valor no es un identificador simple, ponlo entre comillas. Si necesitas estructuras complejas, usa JSON: htmx acepta ambos en los mismos atributos.

Opciones de configuración

Las que vas a tocar más seguido:

OpciónPor defectoPara qué sirve
defaultSwapinnerHTMLEstrategia de swap global
defaultTimeout60000Timeout de las peticiones en ms
transitionsfalseActiva la View Transitions API
historytruetrue, false o "reload"
mode'same-origin' restringe peticiones al mismo origen
implicitInheritancefalseRestaura la herencia implícita de htmx 2
noSwap[204, 304]Códigos que no producen swap
extensionsAllowlist de extensiones permitidas
defaultSettleDelayRetardo de la fase de settle
indicatorClass / requestClasshtmx-indicator / htmx-requestClases de los indicadores
morphIgnore / morphSkipSelectores que el morphing no toca
prefixhx-Prefijo de los atributos
logAllfalseLoguea todos los eventos htmx en consola

logAll es la herramienta de depuración más útil del curso:

htmx.config.logAll = true;

Con eso la consola te muestra cada evento con su detail, y entiendes exactamente por qué htmx hizo o no hizo algo.

Estructura de proyecto recomendada

flowchart TD
    R[proyecto/] --> P[public/]
    R --> V[views/]
    R --> S[servidor.js]
    P --> J[js/htmx.min.js]
    P --> C[css/estilos.css]
    V --> L[layout.html]
    V --> F[fragmentos/]

La carpeta clave es views/fragmentos/: en una aplicación htmx, buena parte de tus plantillas son fragmentos de HTML que no son páginas completas. Un <tr>, una lista, un mensaje de error. Organizarlos aparte desde el día uno evita el caos.

Verificar que está funcionando

En la consola del navegador:

htmx.version          // "4.0.0-beta6"
htmx.config           // el objeto de configuración completo

Si htmx es undefined, el script no cargó: revisa la ruta, el integrity (un hash desactualizado bloquea la carga silenciosamente en la consola de red) y que no haya un CSP bloqueándolo.

Resumen

  • CDN para prototipos, self-hosted para producción, npm si ya tienes bundler.
  • La versión actual es 4.0.0-beta6.
  • Valores por defecto: trigger según el elemento, target el propio elemento, swap innerHTML, timeout 60 s.
  • Configura con <meta name="htmx-config"> (antes del script) o con htmx.config en JS.
  • HCON es la sintaxis clave:valor que verás en todos los atributos de htmx 4.
  • htmx.config.logAll = true es tu mejor herramienta de depuración.

Siguiente: Peticiones: hx-get, hx-post y familia →.