HTMX Polling
Polling means sending a request to the server on a fixed time interval to check for new data. It is the simplest way to build a live-updating UI — a sports score ticker, a server health dashboard, a queue status page, or a notification counter. HTMX builds polling into the trigger system with one small addition: the every keyword.
When to Use Polling
| Use Case | Polling Good? | Reason |
|---|---|---|
| Sports score every 5 seconds | Yes | Low frequency, acceptable overhead |
| Server CPU dashboard every 2 seconds | Yes | Near-real-time is good enough |
| Chat messages every 500ms | No | Too frequent — use WebSockets or SSE instead |
| Order status every 10 seconds | Yes | Infrequent changes, simple to implement |
| Collaborative editing cursor positions | No | Needs sub-second updates — use WebSockets |
Basic Polling Syntax
<div hx-get="/live-score" hx-trigger="every 5s" hx-target="this" hx-swap="innerHTML"> Loading score... </div>
HTMX sends GET /live-score every 5 seconds. The server returns the latest score HTML and HTMX replaces the div's content. The interval runs continuously until the element is removed from the DOM or the user leaves the page.
Timeline: 0s → GET /live-score → "Home 1 – Away 0" 5s → GET /live-score → "Home 1 – Away 0" (no change) 10s → GET /live-score → "Home 2 – Away 0" (goal scored!) 15s → GET /live-score → "Home 2 – Away 1" ...
Polling Interval Units
| Syntax | Interval |
|---|---|
| every 1s | Every 1 second |
| every 30s | Every 30 seconds |
| every 2m | Every 2 minutes |
| every 500ms | Every 500 milliseconds |
Combining Polling With Load Trigger
Start polling immediately when the element loads, and also fire once on load to populate it right away:
<div hx-get="/notifications/count" hx-trigger="load, every 60s" hx-target="this"> ... </div>
The first load trigger fires once immediately. The every 60s trigger then keeps it refreshing once a minute.
Server-Driven Polling Stop
The server can stop the polling loop by returning the response header HX-Trigger: stop-polling along with the response. Pair this with a custom event listener on the element:
<div id="job-status" hx-get="/job/42/status" hx-trigger="every 3s" hx-target="this" hx-on:stop-polling="htmx.removeAttribute(this, 'hx-trigger')"> Checking job status... </div>
Server response when the job finishes:
# Flask
response = Response("<p>Job complete! ✓</p>")
response.headers['HX-Trigger'] = 'stop-polling'
return response
Flow:
0s → GET /job/42/status → "Processing... (20%)"
3s → GET /job/42/status → "Processing... (60%)"
6s → GET /job/42/status → "Processing... (90%)"
9s → GET /job/42/status → "Job complete! ✓"
+ HX-Trigger: stop-polling
|
v
stop-polling event fires
hx-trigger attribute removed from element
Polling stops — no more requests
Conditional Polling Based on Response
Another approach is to have the server return a new element with or without the polling trigger, depending on the current state:
# Flask
@app.route('/job/<int:job_id>/status')
def job_status(job_id):
job = Job.query.get(job_id)
if job.status == 'complete':
# Return element WITHOUT polling trigger — polling stops naturally
return f'''
<div id="job-status" hx-swap-oob="true">
<p>Job complete! ✓</p>
</div>
'''
else:
# Return element WITH polling trigger — polling continues
return f'''
<div id="job-status"
hx-get="/job/{job_id}/status"
hx-trigger="every 3s"
hx-target="this"
hx-swap="outerHTML">
Processing... ({job.progress}%)
</div>
'''
When the job is complete, the server returns a div without hx-trigger. HTMX replaces the polling element with the finished-state element, and polling stops because the new element has no trigger.
Full Dashboard Polling Example
<h2>System Dashboard</h2> <div hx-get="/stats/cpu" hx-trigger="load, every 2s" hx-target="this">CPU: --</div> <div hx-get="/stats/memory" hx-trigger="load, every 5s" hx-target="this">RAM: --</div> <div hx-get="/stats/disk" hx-trigger="load, every 30s" hx-target="this">Disk: --</div>
Each stat polls at its own interval. CPU updates every 2 seconds (fast-changing), disk every 30 seconds (slow-changing). Each element manages its own polling loop independently.
Polling and Server Load
Each polling element sends a request on every interval. With 1,000 visitors and three polling elements each firing every 5 seconds, your server receives 600 requests per second just for polling. Keep these points in mind:
- Use the longest interval that still feels live for your use case.
- Cache polling responses on the server when the data changes less often than the polling interval.
- Consider Server-Sent Events (SSE) for high-frequency updates — the server pushes data only when it changes, eliminating wasted requests.
- Stop polling for hidden tabs using the Page Visibility API in JavaScript.
Stopping Polling When Tab Is Hidden
<script>
document.addEventListener('visibilitychange', function() {
const poller = document.getElementById('live-score');
if (document.hidden) {
// Remove trigger to pause polling
poller.removeAttribute('hx-trigger');
} else {
// Restore trigger to resume polling
poller.setAttribute('hx-trigger', 'every 5s');
htmx.process(poller); // Re-initialize HTMX on the element
}
});
</script>
Key Takeaway
HTMX polling uses the every Xs trigger syntax to send requests on a fixed interval. It is the simplest path to live-updating content. Stop polling by removing the hx-trigger attribute, by having the server return a response without a polling trigger, or by sending the HX-Trigger: stop-polling response header. Keep intervals as long as practical to minimize unnecessary server load, and pause polling when the tab is hidden to save both server and client resources.
