Production Patterns
When to Use
Reference this for proven HTMX implementations of common UI patterns: load more, infinite scroll, modals, real-time updates, and live search.
Decision
| Pattern | Key attributes | Notes |
|---|---|---|
| Load more | swap('beforeend'), select() |
select() prevents button from being appended |
| Infinite scroll | trigger('revealed') |
Fires when element scrolls into view |
| Modal content | target('#modal'), swap('innerHTML') |
Modal container must already exist on page |
| Real-time polling | trigger('every 5s') |
Combine with caching to reduce server load |
| Live search | trigger('keyup changed delay:500ms') |
Debounce prevents requests on every keystroke |
Pattern
Load more / pagination:
(new Htmx())
->get(Url::fromRoute('my.load_more', ['page' => $page + 1]))
->target('#items-container')
->swap('beforeend')
->select('#items-container > *')
->applyTo($build['load_more']);
Infinite scroll:
$build['sentinel'] = ['#type' => 'html_tag', '#tag' => 'div'];
(new Htmx())
->get(Url::fromRoute('my.load_more', ['page' => $page + 1]))
->trigger('revealed')
->target('#items-container')
->swap('beforeend')
->select('#items-container > *')
->applyTo($build['sentinel']);
Modal content on demand:
(new Htmx())
->get(Url::fromRoute('my.modal_content', ['id' => $entity_id]))
->target('#modal-content')
->swap('innerHTML')
->onlyMainContent()
->applyTo($build['open_modal']);
Real-time polling:
(new Htmx())
->get(Url::fromRoute('my.status'))
->trigger('every 5s')
->target('#status-display')
->swap('outerHTML')
->onlyMainContent()
->applyTo($build['status']);
Live search with debounce:
(new Htmx())
->get(Url::fromRoute('my.search'))
->trigger('keyup changed delay:500ms')
->target('#search-results')
->swap('innerHTML')
->include('[name="search"]')
->indicator('#spinner')
->onlyMainContent()
->applyTo($form['search']);
Common Mistakes
- Wrong: Not using
swap('beforeend')for append operations → Right:innerHTMLreplaces instead of appending - Wrong: Forgetting
select()for load more → Right: Entire response (including the button) gets appended - Wrong: Not debouncing live search → Right: Creates too many requests while typing
- Wrong: Using polling without considering server load → Right: Combine with cache strategy
- Wrong: Not providing loading indicators → Right: Users don't know request is in progress