htmx: Build Interactive Web Apps with HTML · Hypermedia Foundations
Triggers and Timing
Build a notifications badge that polls, a section that loads lazily, and a search box that waits for the user to pause.
What you'll build
Three small features on a dashboard header: a notifications badge that refreshes every few seconds, a slow section that loads after the page appears, and a username check that runs only when the user stops typing.
The idea
hx-trigger says when to send a request. Without it, inputs use change, forms use submit, and everything else
uses click. You can use any DOM event plus:
| Syntax | Meaning |
|---|---|
input changed delay:400ms |
debounce: wait for a pause, skip unchanged values |
click throttle:2s |
at most one request per 2 seconds |
load |
as soon as the element is on the page |
revealed / intersect once |
when the element scrolls into view |
every 5s |
polling |
keyup[key=='Enter'] |
an event with a filter |
myEvent from:body |
an event fired anywhere on the page |
The code
<p>Notifications: <strong hx-get="/notifications/count" hx-trigger="load, every 3s">0</strong></p>
<div hx-get="/dashboard/stats" hx-trigger="load">Loading statistics...</div>
<label>Choose a username
<input name="username" hx-get="/username/check" hx-trigger="input changed delay:400ms" hx-target="#name-msg">
</label>
<span id="name-msg"></span>
<!-- The pretend server: on a real site this part is your backend (Python, PHP, Node, Go...) -->
<script>
let unread = 0;
server.get("/notifications/count", () => String(unread += Math.random() < 0.5 ? 1 : 0));
server.get("/dashboard/stats", () => "<p>Orders today: 42 · Revenue: $1,980</p>", { delay: 1500 });
server.get("/username/check", (req) => {
const name = (req.query.username || "").trim();
if (name.length < 3) return "At least 3 characters.";
return ["admin", "root", "ada"].includes(name.toLowerCase()) ? `"${server.escape(name)}" is taken.` : "Available!";
});
</script>
Type quickly in the username box and watch the log: one request per pause, not one per key.
Common mistakes
hx-trigger="keyup"on a search box withoutdelay: a request per keystroke, and responses can arrive out of order.- Polling too often. Every open tab sends requests; choose the slowest interval users accept, or use SSE.
- Using
loadfor content that's needed immediately; render it in the page instead, and keeploadfor slow parts.
Key points
- Pick the trigger that matches the user's intent; debounce typing.
loadandrevealedmake slow content lazy.everypolls; the server can stop it with status 286.
Further reading: hx-trigger · Tutorial: Triggers
Practice questions
What is the difference between delay:500ms and throttle:500ms?
Answer
delay waits until events stop for 500 ms and sends once (debounce); throttle sends at most once every 500 ms while events keep coming.
Why add the changed modifier to a search input's trigger?
Answer
So no request is sent when the event fires but the value is the same (for example arrow keys or re-typing the same text).
Which trigger makes a slow dashboard section load after the rest of the page appears?
Answer
load: the request fires when htmx processes the element, so the page shows first and the section fills in.
Open in the app to mark lessons complete, take notes and track your progress.