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:

FormatExample
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

SituationBest ChoiceReason
Simple one-liner for one elementhx-onKeeps logic near the element; readable
Same behavior on many elementsaddEventListener on parentOne listener handles all children via delegation
Complex multi-line logicaddEventListenerJavaScript functions are easier to read and test
Global event (CSRF, analytics)addEventListener on documentOne 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.

Leave a Comment

Your email address will not be published. Required fields are marked *