HTMX hx-on Attribute
The hx-on attribute lets you respond to HTMX events directly inside your HTML — without writing a separate JavaScript addEventListener call. It is the inline event handler for HTMX, similar to how onclick works for standard DOM events. This keeps your event logic close to the element it applies to, making small behaviors easy to read and maintain.
Basic Syntax
hx-on:EVENT-NAME="javascript expression"
The event name follows the colon. The value is any valid JavaScript expression or statement. The keyword this refers to the element that holds the attribute.
<button hx-post="/save" hx-target="#result" hx-on:htmx:after-request="this.textContent = 'Saved!'"> Save </button>
After the save request completes, the button's label changes from "Save" to "Saved!" without any external JavaScript.
Event Name Formatting
HTMX event names use camelCase (e.g., htmx:afterRequest). In HTML attributes, use kebab-case (hyphens) because colons inside attribute values need special handling in some parsers. Both formats work in modern browsers:
| Format | Example |
|---|---|
| camelCase (JavaScript) | addEventListener('htmx:afterRequest', ...) |
| kebab-case (HTML attribute) | hx-on:htmx:after-request="..." |
Common hx-on Patterns
Reset a Form After Successful Submit
<form hx-post="/comments" hx-target="#comment-list" hx-swap="beforeend" hx-on:htmx:after-request="if(event.detail.successful) this.reset()"> <textarea name="body" placeholder="Add a comment..."></textarea> <button type="submit">Post</button> </form>
Show a Toast Notification
<button
hx-post="/like/42"
hx-target="#like-count-42"
hx-on:htmx:after-request="showToast('Post liked!')">
♡ Like
</button>
<script>
function showToast(msg) {
const t = document.createElement('div');
t.textContent = msg;
t.style.cssText = 'position:fixed;bottom:20px;right:20px;background:#333;color:#fff;padding:10px 20px;border-radius:4px;';
document.body.appendChild(t);
setTimeout(() => t.remove(), 3000);
}
</script>
Focus the Next Input After Response
<input type="text" name="item" hx-post="/add-item" hx-target="#item-list" hx-swap="beforeend" hx-trigger="keyup[key=='Enter']" hx-on:htmx:after-swap="this.value = ''; this.focus()">
Log Requests for Debugging
<div
hx-get="/data"
hx-trigger="load"
hx-target="this"
hx-on:htmx:before-request="console.log('Fetching /data...')"
hx-on:htmx:after-request="console.log('Done:', event.detail.successful)">
Loading...
</div>
Listening to DOM Events With hx-on
The hx-on attribute is not limited to HTMX events. It works with any DOM event, making it a universal inline event handler:
<!-- Run code on a standard click event -->
<button hx-on:click="alert('Button clicked!')">Click Me</button>
<!-- Run code when input changes -->
<input type="text" hx-on:input="console.log(this.value)">
<!-- Run code when a select changes -->
<select hx-on:change="document.getElementById('preview').textContent = this.value">
<option value="Red">Red</option>
<option value="Blue">Blue</option>
</select>
<span id="preview"></span>
Multiple hx-on Handlers
You can attach multiple event handlers on the same element. Each gets its own hx-on:event-name attribute:
<button hx-post="/action" hx-target="#output" hx-on:htmx:before-request="this.disabled = true" hx-on:htmx:after-request="this.disabled = false"> Submit </button>
The button disables itself when the request starts and re-enables when it completes — preventing double clicks without a separate CSS class or external script.
hx-on vs addEventListener: When to Use Each
| Situation | Best Choice | Reason |
|---|---|---|
| Simple one-liner for one element | hx-on | Keeps logic near the element; readable |
| Same behavior on many elements | addEventListener on parent | One listener handles all children via delegation |
| Complex multi-line logic | addEventListener | JavaScript functions are easier to read and test |
| Global event (CSRF, analytics) | addEventListener on document | One place handles all HTMX requests site-wide |
Real-World Example: Delete With Confirmation and Feedback
<li id="note-3">
Meeting notes — Jan 5
<button
hx-delete="/notes/3"
hx-target="#note-3"
hx-swap="outerHTML"
hx-confirm="Delete this note?"
hx-on:htmx:before-request="this.textContent = 'Deleting...'"
hx-on:htmx:send-error="this.textContent = '✕ Delete'; alert('Delete failed.')">
✕ Delete
</button>
</li>
Diagram:
User clicks [✕ Delete]
|
Native confirm dialog appears → user clicks OK
|
htmx:beforeRequest fires → button text changes to "Deleting..."
|
DELETE /notes/3 sent
|
(success) → list item removed by outerHTML swap
(network error) → htmx:sendError fires → button resets + alert shown
Key Takeaway
The hx-on attribute embeds event-handling logic directly in your HTML elements. Use it for short, element-specific behaviors like resetting a form, updating a label, disabling a button, or logging to the console. For shared or complex behavior, use addEventListener in a script block instead. Both approaches listen to the same HTMX event system — hx-on is simply the inline shorthand for cases where keeping logic near the element makes the code clearer.
