Instalación y primer proyecto con htmx 4
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
- Descarga
https://cdn.jsdelivr.net/npm/[email protected]/dist/htmx.min.js. - Guárdalo en
public/js/htmx.min.js. - 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:
| Aspecto | Valor por defecto | Atributo que lo cambia |
|---|---|---|
| Evento que dispara | click (en un <button>) | hx-trigger |
| Destino | el propio elemento | hx-target |
| Inserción | innerHTML | hx-swap |
| Timeout | 60 000 ms | htmx.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ón | Por defecto | Para qué sirve |
|---|---|---|
defaultSwap | innerHTML | Estrategia de swap global |
defaultTimeout | 60000 | Timeout de las peticiones en ms |
transitions | false | Activa la View Transitions API |
history | true | true, false o "reload" |
mode | — | 'same-origin' restringe peticiones al mismo origen |
implicitInheritance | false | Restaura la herencia implícita de htmx 2 |
noSwap | [204, 304] | Códigos que no producen swap |
extensions | — | Allowlist de extensiones permitidas |
defaultSettleDelay | — | Retardo de la fase de settle |
indicatorClass / requestClass | htmx-indicator / htmx-request | Clases de los indicadores |
morphIgnore / morphSkip | — | Selectores que el morphing no toca |
prefix | hx- | Prefijo de los atributos |
logAll | false | Loguea 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 conhtmx.configen JS. - HCON es la sintaxis
clave:valorque verás en todos los atributos de htmx 4. htmx.config.logAll = truees tu mejor herramienta de depuración.
Siguiente: Peticiones: hx-get, hx-post y familia →.