Targets y selectores extendidos

Por: Artiko
htmxhtmx4hx-targetselectoresdom

Targets y selectores extendidos

hx-target responde a dónde va la respuesta. Si no lo escribes, el destino es el propio elemento que disparó la petición.

<!-- Se reemplaza a sí mismo -->
<button hx-get="/hora">¿Qué hora es?</button>

<!-- Reemplaza otro elemento -->
<button hx-get="/hora" hx-target="#reloj">¿Qué hora es?</button>
<div id="reloj"></div>

Selectores CSS estándar

Cualquier selector CSS válido funciona, y se resuelve sobre todo el documento:

<button hx-get="/datos" hx-target="#resultados">Por id</button>
<button hx-get="/datos" hx-target=".contenedor">Por clase (el primero que coincida)</button>
<button hx-get="/datos" hx-target="table tbody">Por descendencia</button>
<button hx-get="/datos" hx-target="[data-panel='ventas']">Por atributo</button>

Cuando el selector coincide con varios elementos, htmx usa el primero.

El problema de los ids únicos

En una lista generada por el servidor, escribir hx-target="#fila-42" te obliga a generar ids únicos para todo. Funciona, pero se vuelve frágil: si el mismo componente aparece dos veces en la página, los ids chocan.

Los selectores relativos resuelven eso: describen el destino en relación al elemento que dispara, no por su identidad.

flowchart TD
    A["&lt;div class='card'&gt;"] --> B["&lt;div class='card-body'&gt;"]
    B --> C["&lt;button&gt; ← dispara"]
    B --> D["&lt;span class='estado'&gt;"]
    A --> E["&lt;footer&gt;"]
    C -.->|"closest .card"| A
    C -.->|"find .estado"| B
    C -.->|"next"| D

Tabla de selectores extendidos

SelectorQué apunta
thisEl propio elemento (el valor por defecto)
closest <sel>El ancestro más cercano que coincide (incluye el propio elemento)
find <sel>El primer descendiente que coincide
findAll <sel>Todos los descendientes que coinciden
nextEl hermano siguiente
next <sel>El primer hermano posterior que coincide
previousEl hermano anterior
previous <sel>El primer hermano anterior que coincide
global:<sel>Selector CSS global, atravesando el shadow DOM

closest

El más usado. Un botón que actualiza la tarjeta que lo contiene:

<div class="card">
  <div class="card-body">
    <h3>Pedido #481</h3>
    <button hx-get="/pedidos/481" hx-target="closest .card" hx-swap="outerHTML">
      Actualizar
    </button>
  </div>
</div>

El mismo HTML sirve para cien tarjetas: no hay ids que generar.

Aplicado a filas de tabla:

<tr>
  <td>Ana</td>
  <td>
    <button hx-delete="/usuarios/7" hx-target="closest tr" hx-swap="delete">
      Eliminar
    </button>
  </td>
</tr>

find y findAll

Buscan hacia abajo. Útil cuando el disparador es el contenedor:

<div class="widget" hx-get="/widget/datos" hx-trigger="load" hx-target="find .contenido">
  <h4>Ventas del mes</h4>
  <div class="contenido">Cargando…</div>
</div>

findAll inserta la respuesta en todos los descendientes que coincidan:

<div hx-get="/precio" hx-target="findAll .precio">
  <span class="precio">—</span>
  <span class="precio">—</span>
</div>

next y previous

Trabajan sobre hermanos. Sin selector, el hermano inmediato:

<button hx-get="/detalle/1" hx-target="next">Ver detalle</button>
<div></div>

Con selector, el primer hermano en esa dirección que coincida:

<button hx-get="/detalle/1" hx-target="next .panel">Ver detalle</button>
<span>texto que se salta</span>
<div class="panel"></div>

Es el patrón natural para acordeones y “mostrar más”:

<ul>
  <li>
    <button hx-get="/faq/1/respuesta" hx-target="next .respuesta">¿Cómo funciona?</button>
    <div class="respuesta"></div>
  </li>
  <li>
    <button hx-get="/faq/2/respuesta" hx-target="next .respuesta">¿Cuánto cuesta?</button>
    <div class="respuesta"></div>
  </li>
</ul>

global:

Los selectores de htmx se resuelven dentro del árbol donde vive el elemento. Si estás dentro de un shadow DOM (un web component), #modal no encontrará un elemento del documento principal. global: fuerza la búsqueda en el documento raíz:

<button hx-get="/aviso" hx-target="global:#notificaciones">Avisar</button>

Combinar target con selección de la respuesta

hx-target decide dónde va; hx-select decide qué parte de la respuesta se usa. Son independientes y se combinan:

<button hx-get="/reporte-completo"
        hx-select="#tabla-resumen"
        hx-target="#panel">
  Ver resumen
</button>

El servidor devuelve una página entera, htmx extrae sólo #tabla-resumen y lo inserta en #panel. Es la manera de reutilizar páginas existentes como fuente de fragmentos sin tocar el backend. El capítulo 8 profundiza en hx-select y hx-select-oob.

Reasignar el target desde el servidor

El servidor puede sobrescribir el target de una respuesta concreta con el header HX-Retarget:

// Si la validación falla, mandamos el error al panel de errores
// en vez de al target original
return new Response(`<p class="error">El RUT ya existe</p>`, {
  status: 409,
  headers: {
    "Content-Type": "text/html",
    "HX-Retarget": "#errores",
    "HX-Reswap": "innerHTML",
  },
});

Esto evita tener que declarar en el HTML todos los destinos posibles de una acción.

Herencia del target

En htmx 4 la herencia es explícita. Para que un contenedor imponga el target a todos sus hijos:

<div hx-target:inherited="#panel-principal">
  <button hx-get="/a">A</button>
  <button hx-get="/b">B</button>
  <button hx-get="/c" hx-target="#otro">C — anula la herencia</button>
</div>

Sin :inherited, cada botón sin hx-target apuntaría a sí mismo. Este es el error número uno al venir de htmx 2 y se cubre completo en el capítulo 7.

Elegir el target correcto

flowchart TD
    A{¿Qué actualizas?} --> B[El propio elemento]
    A --> C[Un contenedor ancestro]
    A --> D[Algo dentro del disparador]
    A --> E[Un hermano]
    A --> F[Una zona fija de la página]
    B --> B1["hx-target='this' — o nada"]
    C --> C1["hx-target='closest .card'"]
    D --> D1["hx-target='find .contenido'"]
    E --> E1["hx-target='next .panel'"]
    F --> F1["hx-target='#panel'"]

Regla práctica: prefiere selectores relativos cuando el HTML se repite (listas, tablas, tarjetas) y selectores por id cuando la zona es única en la página (una barra lateral, un contador del header, un modal).

Errores comunes

SíntomaCausaSolución
El contenido aparece dentro del botónNo hay hx-target y el default es thisDeclara el target
#fila-3 no existe al segundo clicUn swap anterior borró el elementoUsa closest tr en lugar del id
El target no se encuentra en un web componentEstás dentro de un shadow rootUsa global:#destino
Los hijos no heredan el targethtmx 4 no hereda por defectoUsa hx-target:inherited
next no encuentra nadaEl elemento no es hermano sino sobrinoUsa closest + find

Resumen

  • Por defecto, el target es el propio elemento.
  • Acepta cualquier selector CSS, resuelto sobre el documento.
  • Los selectores relativos (closest, find, findAll, next, previous) evitan generar ids únicos y hacen el HTML reutilizable.
  • global: atraviesa el shadow DOM.
  • hx-select recorta la respuesta; hx-target elige el destino: son ortogonales.
  • HX-Retarget permite al servidor redirigir la respuesta a otro elemento.
  • La herencia del target requiere hx-target:inherited en htmx 4.

Siguiente: Swaps y morphing →.