Catálogo de extensiones
Catálogo de extensiones
htmx 4 mantiene el core pequeño y mueve todo lo demás a extensiones. La API de extensiones también
creció: ahora pueden intervenir en todo el ciclo de petición, respuesta y swap, e incluso
reemplazar la implementación de fetch().
Cómo se cargan (cambió en htmx 4)
hx-ext fue eliminado. Ahora basta con incluir el script de la extensión:
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/htmx.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/ext/hx-preload.js"></script>
Y opcionalmente declarar cuáles se permiten, como medida de seguridad:
<meta name="htmx-config" content='extensions:"hx-preload, hx-sse"'>
Con ese allowlist, un script inyectado no puede registrar una extensión maliciosa.
flowchart LR
A["htmx 2: hx-ext='sse' en el HTML"] --> B["htmx 4: incluir el script<br/>+ allowlist en la meta"]
El catálogo
Red
| Extensión | Qué hace |
|---|---|
hx-sse | Stream de HTML con text/event-stream |
hx-ws | HTML bidireccional sobre WebSockets |
hx-multipart | Respuestas en partes con multipart/mixed |
Cubiertas en el capítulo 14.
Experiencia de usuario
| Extensión | Qué hace |
|---|---|
hx-live | Bindings reactivos sobre atributos, clases, texto y estilos |
hx-optimistic | Muestra el resultado esperado antes de la respuesta |
hx-browser-indicator | Indica actividad en el título/favicon de la pestaña |
hx-prompt | Pide un valor al usuario antes de la petición |
Rendimiento
| Extensión | Qué hace |
|---|---|
hx-preload | Precarga al pasar el mouse |
hx-ptag | Evita polling innecesario |
hx-history-cache | Restaura páginas desde sessionStorage |
Swaps
| Extensión | Qué hace |
|---|---|
hx-head | Fusiona etiquetas del <head> de la respuesta |
hx-upsert | Actualiza o inserta según exista el elemento |
hx-targets | Selecciona múltiples destinos |
hx-download | Descarga archivos desde una petición htmx |
Compatibilidad y seguridad
| Extensión | Qué hace |
|---|---|
htmx-2-compat | Restaura comportamientos de htmx 2 |
hx-alpine-compat | Convivencia con Alpine.js |
hx-csp | Funcionamiento bajo CSP estricta |
hx-live: reactividad declarativa
La más ambiciosa. Añade bindings reactivos al HTML, cubriendo el hueco que muchos proyectos llenaban con Alpine.js.
<script src="/js/ext/hx-live.min.js"></script>
Bindings de atributo
<input id="nombre">
<button :disabled="!q('#nombre').value">Enviar</button>
El botón se habilita solo cuando el input tiene contenido. Sin listeners, sin estado.
Clases
<!-- Forma de objeto -->
<div :class="{ alerta: edad < 18, ok: edad >= 18 }"></div>
<!-- Una clase concreta -->
<p :.advertencia="saldo < 0">Saldo negativo</p>
Texto y HTML
<input id="cantidad" type="number" value="1">
<p :text="q('#cantidad').valueAsNumber * 4990"></p>
<div :html="`<b>${valor}</b>`"></div>
Estilos
<input id="pct" type="range" value="50">
<div :style="{ width: q('#pct').value + '%', backgroundColor: '#ec4899' }"></div>
Escape hatch: hx-live
Para lógica que no cabe en una expresión:
<div hx-live="
let termino = q('input').value;
if (!termino) return;
await debounce(250);
this.textContent = await fetch('/buscar?q=' + termino).then(r => r.text());
"></div>
API pública
| Función | Qué hace |
|---|---|
htmx.live.q(selector) | Selecciona elementos con lectura/escritura reactiva |
htmx.live.attr(selector, nombre, valor?) | Lee o escribe atributos |
htmx.live.take(clase, ámbito?) | Mueve una clase de un elemento a otro (tabs, selección) |
htmx.live.forEvent(...) | Vincula a un evento |
htmx.live.nextFrame() | Espera al siguiente frame |
htmx.live.debounce(ms) | Debounce dentro de expresiones |
htmx.live.refresh() | Fuerza el recálculo |
Cuando cambias estado desde fuera del DOM, avisa manualmente:
window.estadoApp = "cargando";
htmx.live.refresh();
Combinado con htmx
<form :aria-busy="matches('.htmx-request')" hx-post="/guardar">
<input name="email">
<button type="submit">Guardar</button>
</form>
El formulario expone su estado de carga a los lectores de pantalla sin JavaScript propio.
htmx.live.take resuelve el patrón de pestañas de manera elegante:
<nav>
<button class="activa" hx-on:click="htmx.live.take('activa')" hx-get="/tab/1" hx-target="#panel">Uno</button>
<button hx-on:click="htmx.live.take('activa')" hx-get="/tab/2" hx-target="#panel">Dos</button>
</nav>
<div id="panel"></div>
hx-optimistic: UI optimista
Muestra el resultado esperado inmediatamente y lo reemplaza cuando llega la respuesta real.
<script src="/js/ext/hx-optimistic.js"></script>
<meta name="htmx-config" content='extensions:"hx-optimistic"'>
<ul id="mensajes">
<li>Hola mundo</li>
</ul>
<template id="tpl-enviando">
<li>Enviando…</li>
</template>
<form hx-post="/mensajes"
hx-target="#mensajes"
hx-swap="beforeend"
hx-optimistic="#tpl-enviando">
<input name="cuerpo" placeholder="Mensaje…">
<button type="submit">Enviar</button>
</form>
La extensión expone los parámetros de la petición como atributos data-* en el template
instanciado, así que la vista previa puede mostrar lo que el usuario escribió, no un texto genérico.
Estilo del estado provisional:
.hx-optimistic {
opacity: .6;
font-style: italic;
}
sequenceDiagram
participant U as Usuario
participant L as Lista
participant S as Servidor
U->>L: submit
L->>L: inserta el template (clase hx-optimistic)
L->>S: POST /mensajes
S-->>L: <li> real
L->>L: reemplaza la vista previa
Úsalo sólo cuando la operación casi nunca falla. Si falla a menudo, ver aparecer y desaparecer contenido es peor que esperar.
hx-preload
<a href="/tareas" hx-preload>Tareas</a>
Carga el destino al pasar el mouse. Para la navegación principal es una de las mejoras de percepción de velocidad más baratas que existen.
hx-head: fusionar el <head>
Cuando navegas con boost, la respuesta puede traer un <head> con estilos o metadatos distintos.
hx-head los fusiona con el <head> actual en lugar de ignorarlos: útil para meta tags de Open
Graph, hojas de estilo específicas de una sección o scripts por página.
hx-upsert
Da acceso a la estrategia de “actualizar si existe, insertar si no” para elementos identificados
por id. Simplifica los endpoints que no saben si la operación fue creación o edición.
hx-csp: Content Security Policy estricta
Con una CSP sin unsafe-inline ni unsafe-eval, los atributos hx-on, los hx-vals con js:
y las expresiones de hx-live dejan de funcionar. hx-csp proporciona la ruta compatible.
Complementa la opción de configuración inlineScriptNonce, que permite pasar el nonce de tu CSP a
los scripts que htmx procesa. Se cubre en el
capítulo 18.
htmx-2-compat y hx-alpine-compat
htmx-2-compat restaura de una sola vez: herencia implícita, nombres de eventos antiguos y el
comportamiento previo de no hacer swap ante errores. Es la red de seguridad para migrar por partes.
hx-alpine-compat resuelve el conflicto de sintaxis entre hx-live y Alpine.js, que usan el mismo
prefijo : para bindings. Si tu proyecto ya usa Alpine y quieres incorporar hx-live gradualmente,
esta es la pieza.
Escribir tu propia extensión
htmx.registerExtension("mi-extension", {
onEvent(nombre, evento) {
if (nombre === "htmx:config:request") {
evento.detail.ctx.request.headers["X-Traza"] = crypto.randomUUID();
}
},
});
En htmx 4 las extensiones tienen acceso a HCON para parsear su propia configuración, y pueden
sustituir la función de red, lo que abre la puerta a caché offline, reintentos con backoff o
transporte alternativo.
Elegir extensiones
flowchart TD
A{¿Qué te falta?} --> B[Reactividad en el cliente]
A --> C[Sensación de instantaneidad]
A --> D[Tiempo real]
A --> E[Compatibilidad]
B --> B1["hx-live"]
C --> C1["hx-preload + hx-optimistic"]
D --> D1["hx-sse / hx-ws"]
E --> E1["htmx-2-compat / hx-alpine-compat / hx-csp"]
Recomendación: no cargues extensiones “por si acaso”. Cada una es JavaScript que se descarga y ejecuta. Empieza sin ninguna y añade cuando tengas el problema concreto delante.
Errores comunes
| Síntoma | Causa | Solución |
|---|---|---|
hx-ext="sse" no hace nada | hx-ext fue eliminado en htmx 4 | Incluir el script de la extensión |
| La extensión no se activa | No está en el allowlist | Añadirla a extensions: en la meta |
Conflicto de : con Alpine | Ambos usan el mismo prefijo | hx-alpine-compat |
hx-live no reacciona a un cambio externo | El estado no está en el DOM | htmx.live.refresh() |
| La vista optimista se queda pegada | La petición falló sin respuesta | Manejar htmx:error |
Resumen
hx-extdesapareció: cargar el script basta, yextensions:en la meta actúa como allowlist.hx-liveaporta reactividad declarativa::attr,:class,:text,:html,:styley la APIhtmx.live.*.hx-optimisticmuestra el resultado antes de la respuesta usando un<template>.hx-preloadprecarga al hover;hx-headfusiona metadatos;hx-upsertsimplifica crear/editar.htmx-2-compat,hx-alpine-compatyhx-cspcubren compatibilidad.- Las extensiones de htmx 4 pueden interceptar todo el ciclo e incluso reemplazar
fetch().
Siguiente: Proyecto: CRUD completo →.