htmx 4.0 is under construction — migration guide

The hx-trigger attribute controls which event(s) trigger an element’s AJAX request (set via hx-get, hx-post, etc.).

Defaults to:

  • change<input> / <textarea> / <select>
  • submit<form>
  • click<input type=button>, <input type=submit>, and everything else

Syntax

<!-- hx-trigger="<event>[<filter>] <modifiers> [, ...]" --> <!-- Basic (click is the default) --> <button hx-get="..."> <!-- With from: modifier — listen on a different element --> <button hx-trigger="click from:outside" hx-get="..."> <!-- With a filter --> <button hx-trigger="click[shiftKey]" hx-get="..."> <!-- Multiple triggers --> <button hx-trigger="click, keyup[key=='Enter']" hx-get="...">

Standard Events

hx-trigger accepts any DOM event: click, input, keyup, submit, etc.

<button hx-trigger="click" hx-post="..."> <input hx-trigger="input" hx-get="..."> <form hx-trigger="submit" hx-post="..."> <div hx-trigger="mouseenter" hx-get="...">

Custom Events

Custom events work too. Dispatch them from JavaScript with htmx.trigger(), or from the server via the HX-Trigger response header.

Events from HX-Trigger are dispatched on the body, so use from:body to listen for them:

<div hx-trigger="productsUpdated from:body" hx-get="...">...</div>

Synthetic Events

htmx provides synthetic events beyond standard DOM events:

load

Fires when the element is loaded into the DOM. Useful for lazy-loading content.

<div hx-trigger="load" hx-get="...">Loading...</div>

revealed

Fires when the element is scrolled into the viewport. Useful for infinite scroll.

<div hx-trigger="revealed" hx-get="...">Loading...</div>

Note: revealed always observes the browser viewport. For scrollable containers with overflow, use intersect with root instead.

intersect

Fires when an element becomes visible in the viewport.

Uses the IntersectionObserver API and supports root, rootMargin, and threshold as modifiers.

<div hx-trigger="intersect once" hx-get="...">...</div> <div hx-trigger="intersect root:#scroll-container" hx-get="...">...</div> <div hx-trigger="intersect rootMargin:100px" hx-get="...">...</div> <div hx-trigger="intersect threshold:0.5" hx-get="...">...</div>

every <time>

Fires repeatedly on an interval.

<div hx-trigger="every 1s" hx-get="/updates">...</div>

To add a filter to polling, add it after the interval:

<div hx-trigger="every 1s [someConditional]" hx-get="/updates">...</div>

Event Modifiers

[filter]

A JavaScript expression in brackets after the event name. Only fires when it evaluates to true.

<input hx-trigger="keyup[key == 'Enter']" hx-get="/search">

Inside the brackets, all properties of the event are available as bare names:

  • clickaltKey, ctrlKey, shiftKey, metaKey, …
  • keydownkey, code, repeat, …

Global functions work too: click[hasUnsavedChanges()].

once

Fires once, then stops listening.

<button hx-trigger="click once" hx-get="...">Load Once</button>

changed

Only fires if the element’s value changed since last time.

<input hx-trigger="input changed" hx-get="...">

Note: change is a DOM event. changed is an htmx modifier. Different things.

delay:<time>

Waits before firing. If the event fires again, the delay resets (debounce).

<input hx-trigger="input delay:1s" hx-get="...">

throttle:<time>

Fires, then ignores further events for the given interval.

<div hx-trigger="scroll throttle:500ms" hx-get="...">...</div>

from:<selector>

Listens on a different element. Takes a CSS selector or an extended selector. Two special values: self (only the element itself, not children) and outside (anything outside the element).

<div hx-trigger="keyup[key=='Enter'] from:body" hx-get="...">...</div> <div hx-trigger="my-event from:document" hx-get="...">...</div> <div hx-trigger="submit from:closest form" hx-get="...">...</div> <div hx-trigger="click from:self" hx-get="...">...</div> <div hx-trigger="click from:outside" hx-get="...">...</div>

target:<selector>

Only fires if event.target matches the given CSS selector.

<div hx-trigger="click target:.child-button" hx-get="...">...</div>

prevent

Calls event.preventDefault().

<form hx-trigger="submit prevent" hx-post="...">...</form>

stop / consume

Calls event.stopPropagation().

<button hx-trigger="click stop" hx-get="...">...</button>

halt

Shorthand for prevent stop.

<a hx-trigger="click halt" hx-get="...">...</a>

capture

Listens during the capture phase (top-down) instead of the bubble phase (bottom-up).

<div hx-trigger="click capture" hx-get="...">...</div>

passive

Tells the browser the handler won’t call preventDefault(), so the browser can scroll without waiting for your code to finish.

<div hx-trigger="scroll passive" hx-get="...">...</div>

Example

A search box that searches on input, but only if the value has changed and the user hasn’t typed anything new for 1 second (delay):

<input name="q" hx-trigger="input changed delay:1s" hx-get="/search" hx-target="#search-results"/>

Notes

  • Selectors with whitespace in from or target need parentheses: from:(form input).
  • hx-trigger="change, reset" may fire before the browser resets the form. As a workaround, add a short delay: hx-trigger="change, reset delay:0.01s".

See Also