Common UI patterns that htmx handles with simple HTML attributes. Each replaces dozens of lines of JavaScript.
Click to Load
Load more items by clicking a button. The button replaces itself with new content.
<div id="item-list">
<div class="item">Item 1</div>
<div class="item">Item 2</div>
<div class="item">Item 3</div>
<button hx-get="/items?page=2"
hx-target="#item-list"
hx-swap="beforeend"
hx-select=".item">
Load More
</button>
</div>
<!-- Server returns more .item elements -->
<!-- The button can be included in the response -->
<!-- to chain pagination endlessly -->
Infinite Scroll
Automatically loads content when the user scrolls to the bottom.
<div id="feed">
<div class="post">Post 1</div>
<div class="post">Post 2</div>
<!-- Sentinel element triggers on reveal -->
<div hx-get="/feed?page=2"
hx-trigger="revealed"
hx-target="#feed"
hx-swap="beforeend"
hx-select=".post">
<span class="htmx-indicator">Loading...</span>
</div>
</div>
How It Works
The revealed trigger fires when an element enters the viewport. Include a new sentinel in each response to keep the chain going. Omit it to stop.
Active Search
Live search that updates results as the user types, with debouncing.
<input type="search"
name="q"
placeholder="Search users..."
hx-get="/search"
hx-trigger="input changed delay:300ms, search"
hx-target="#search-results"
hx-indicator="#search-spinner">
<span id="search-spinner" class="htmx-indicator">
Searching...
</span>
<div id="search-results"></div>
The delay:300ms modifier debounces the request. The changed modifier ensures it only fires when the value actually changes.
Inline Editing
Click on content to edit it in place. The view swaps to a form and back.
<!-- View mode -->
<div hx-get="/contact/1/edit"
hx-trigger="click"
hx-swap="outerHTML">
<span>John Doe</span>
<span>john@example.com</span>
</div>
<!-- Server returns edit form: -->
<form hx-put="/contact/1"
hx-target="this"
hx-swap="outerHTML">
<input name="name" value="John Doe">
<input name="email" value="john@example.com">
<button type="submit">Save</button>
<button hx-get="/contact/1"
hx-swap="outerHTML">Cancel</button>
</form>
Delete with Confirmation
<button hx-delete="/items/42"
hx-confirm="Delete this item?"
hx-target="closest tr"
hx-swap="outerHTML swap:500ms">
Delete
</button>
<!-- Server returns 200 with empty body -->
<!-- The row disappears after a 500ms settle -->
Tabs / Navigation
<div class="tabs" hx-target="#tab-content">
<button hx-get="/tabs/profile"
class="active">Profile</button>
<button hx-get="/tabs/settings">Settings</button>
<button hx-get="/tabs/billing">Billing</button>
</div>
<div id="tab-content">
<!-- Tab content loaded here -->
</div>