CtrlK
BlogDocsLog inGet started
Tessl Logo

alonso-skills/htmx

Implements HTMX interactions, configures swap behaviors, debugs hx-* requests, and builds hypermedia-driven UI components. Use when tasks involve hx-* attributes, HTMX AJAX requests, swap strategies, server-sent events, WebSockets, or hypermedia-driven UIs.

95

Quality

95%

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Passed

No known issues

Overview
Quality
Evals
Security
Files

events-api.mdreferences/

description:
HTMX events, JavaScript API, configuration, extensions, and debugging.
globs:
*.html,*.js

HTMX Events & API

Events, JS API, configuration, extensions, debugging, and scripting integration.

Contents

  • Event Reference
  • Event Naming
  • JavaScript API
  • Configuration
  • Extensions
  • Debugging
  • Scripting Integration
  • Third-Party Library Integration

Event Reference

All events are fired on the relevant DOM element and bubble up. Listen with addEventListener, htmx.on(), or hx-on:.

Request Lifecycle Events

EventFires WhenKey event.detail Properties
htmx:confirmFires on every trigger (not just hx-confirm). Call event.preventDefault() to halt, event.detail.issueRequest() to resume. Use for async custom dialogselt, path, verb, target, triggeringEvent, question, issueRequest(skipConfirmation)
htmx:configRequestBefore request. Modify parameters and headers hereparameters, headers, elt, target, verb, path
htmx:beforeRequestBefore AJAX request is madeelt, target, requestConfig, xhr
htmx:beforeSendJust before request is sent over the networkelt, target, requestConfig, xhr
htmx:afterRequestAfter request completes (success or failure)elt, target, xhr, successful, failed
htmx:responseErrorOn non-2xx/3xx HTTP responsexhr, elt, target
htmx:sendErrorOn network failurexhr, elt, target
htmx:sendAbortWhen request is abortedelt, target
htmx:timeoutWhen request times outelt, target, xhr
htmx:abortTrigger this on an element to abort its in-flight request

Response Processing Events

EventFires WhenKey event.detail Properties
htmx:beforeOnLoadBefore any response processingxhr, elt, target
htmx:afterOnLoadAfter successful response processingxhr, elt, target
htmx:onLoadErrorWhen an exception occurs during response processingxhr, elt, target, exception

Swap & Settle Events

EventFires WhenKey event.detail Properties
htmx:beforeSwapBefore swap. Modify swap behavior herexhr, elt, target, shouldSwap, serverResponse, isError, swapOverride, selectOverride, ignoreTitle, requestConfig.elt
htmx:afterSwapAfter new content is swapped inxhr, elt, target
htmx:beforeTransitionBefore View Transition API swap. preventDefault() to cancelxhr, elt, target
htmx:afterSettleAfter DOM has settledxhr, elt, target
htmx:swapErrorWhen swap encounters an errorxhr, elt, target
htmx:targetErrorWhen target selector is invalidtarget

Element Lifecycle Events

EventFires When
htmx:loadNew content added to DOM. Use for initializing third-party libraries
htmx:beforeProcessNodeBefore HTMX initializes attributes on a node
htmx:afterProcessNodeAfter HTMX has initialized a node
htmx:beforeCleanupElementBefore HTMX removes or disables an element

OOB Events

EventFires WhenKey event.detail Properties
htmx:oobBeforeSwapBefore OOB swapfragment, target
htmx:oobAfterSwapAfter OOB swapfragment, target
htmx:oobErrorNoTargetOOB element has no matching id in DOMcontent

History Events

EventFires When
htmx:beforeHistorySaveBefore page snapshot is cached. Clean up DOM here
htmx:pushedIntoHistoryAfter URL is pushed into history
htmx:replacedInHistoryAfter URL is replaced in history
htmx:historyRestoreDuring back/forward navigation restoration
htmx:historyCacheHitHistory cache hit (cached content found)
htmx:historyCacheMissHistory cache miss (AJAX fetch needed)
htmx:historyCacheMissLoadSuccessful AJAX fetch for missed cache
htmx:historyCacheMissLoadErrorFailed AJAX fetch for missed cache
htmx:historyCacheErrorError writing to cache

Validation Events

EventFires When
htmx:validation:validateBefore checkValidity() is called
htmx:validation:failedWhen validation fails
htmx:validation:haltedWhen request is blocked due to validation

XHR Progress Events

EventFires WhenKey event.detail Properties
htmx:xhr:loadstartAJAX request starts
htmx:xhr:progressDuring upload/download progressloaded, total
htmx:xhr:loadendAJAX request ends
htmx:xhr:abortAJAX request aborted

URL & Security Events

EventFires WhenKey event.detail Properties
htmx:validateUrlBefore request, to validate the destination URL. preventDefault() to blockelt, url (URL object), sameHost

Prompt & Trigger Events

EventFires WhenKey event.detail Properties
htmx:promptAfter user responds to hx-prompt dialogelt, target, prompt (user response)
htmx:triggerWhenever an AJAX request occurs (for client-side scripting)elt

SSE Events

EventFires When
htmx:sseOpenSSE connection established
htmx:sseErrorSSE connection error
htmx:sseBeforeMessageBefore SSE message is swapped (cancellable with preventDefault())
htmx:sseMessageAfter SSE message has been swapped in
htmx:sseCloseSSE connection closed. detail.type: nodeMissing, nodeReplaced, or message
htmx:noSSESourceErrorElement references SSE event but no parent SSE source exists

WebSocket Events

EventFires When
htmx:wsConnectingWebSocket connection is being established
htmx:wsOpenWebSocket connection established
htmx:wsCloseWebSocket connection closed
htmx:wsErrorWebSocket connection error
htmx:wsBeforeMessageBefore WebSocket message is processed (cancellable)
htmx:wsAfterMessageAfter WebSocket message has been processed
htmx:wsConfigSendBefore preparing to send over WebSocket (cancellable)
htmx:wsBeforeSendJust before sending over WebSocket (cancellable)
htmx:wsAfterSendAfter message sent over WebSocket

Event Naming

HTMX events support both camelCase and kebab-case:

// Both work
document.addEventListener('htmx:afterSwap', handler);
document.addEventListener('htmx:after-swap', handler);

In hx-on: attributes, use kebab-case (HTML attributes are case-insensitive):

<!-- Correct -->
<div hx-on:htmx:after-swap="handler()">

<!-- Will NOT work (HTML lowercases attributes) -->
<div hx-on:htmx:afterSwap="handler()">

JavaScript API

Element Selection

// Find single element
htmx.find('#my-element');
htmx.find(parentElt, '.child');

// Find all matching elements
htmx.findAll('.items');
htmx.findAll(parentElt, '.items');

// Find closest ancestor
htmx.closest(elt, 'form');

AJAX Requests

Returns a Promise that resolves when the request completes.

// Basic AJAX request
htmx.ajax('GET', '/api/data', '#target');

// With options
htmx.ajax('POST', '/api/data', {
    target: '#target',
    swap: 'innerHTML',
    values: { key: 'value' },
    headers: { 'X-Custom': 'header' },
    source: '#source-element',
    select: '#content',
    selectOOB: '#sidebar',
    push: true,
    replace: false
}).then(() => {
    console.log('Request complete');
});

DOM Manipulation

// Class manipulation (all accept optional delay in ms)
htmx.addClass(elt, 'active');
htmx.addClass(elt, 'active', 1000);  // add after 1s delay
htmx.removeClass(elt, 'active');
htmx.removeClass(elt, 'active', 500);  // remove after 500ms
htmx.toggleClass(elt, 'active');

// Take class from siblings (only this element gets it)
htmx.takeClass(elt, 'selected');

// Remove element from DOM (optional delay in ms)
htmx.remove(elt);
htmx.remove(elt, 2000);  // remove after 2s

// Swap content programmatically
htmx.swap(targetElt, '<p>New content</p>', {
    swapStyle: 'innerHTML',
    swapDelay: 0,
    settleDelay: 20,
    transition: false,
    ignoreTitle: false,
    head: 'merge',  // or 'append'
    scroll: 'top',
    focusScroll: false
}, {
    select: '#content',
    selectOOB: '#sidebar',
    afterSwapCallback: function() {},
    afterSettleCallback: function() {}
});

Event Management

// Listen for events
var listener = htmx.on('#my-element', 'htmx:afterSwap', function(event) {
    console.log('Swapped!', event.detail);
});

// Listen on document
htmx.on('htmx:afterSwap', function(event) {
    console.log('Any swap:', event.detail);
});

// Remove listener
htmx.off('#my-element', 'htmx:afterSwap', listener);

// Trigger events
htmx.trigger(elt, 'myCustomEvent', { key: 'value' });

// Trigger abort on an element's in-flight request
htmx.trigger(elt, 'htmx:abort');

Content Initialization

// Process dynamically-added HTML for HTMX attributes
var newContent = document.createElement('div');
newContent.innerHTML = '<button hx-get="/api">Load</button>';
document.body.appendChild(newContent);
htmx.process(newContent); // Now HTMX recognizes the button

// Initialize third-party libraries on new content
htmx.onLoad(function(content) {
    // Runs every time HTMX swaps new content into the DOM
    var tooltips = content.querySelectorAll('[data-tooltip]');
    tooltips.forEach(initTooltip);
});

Utilities

// Get form values from an element ('get' or 'post' mode)
var values = htmx.values(formElement);
var values = htmx.values(formElement, 'get');

// Parse interval string to milliseconds
htmx.parseInterval('500ms'); // 500
htmx.parseInterval('2s');    // 2000
// Caution: '3m' uses parseFloat, won't parse as minutes

Factory Properties

// Customize SSE EventSource creation
htmx.createEventSource = function(url) {
    return new EventSource(url, { withCredentials: true });
};

// Customize WebSocket creation
htmx.createWebSocket = function(url) {
    return new WebSocket(url, ['wss']);
};

Extension Management

// Define a custom extension
htmx.defineExtension('my-ext', {
    onEvent: function(name, event) {
        if (name === 'htmx:beforeRequest') {
            // Modify request
        }
    },
    transformResponse: function(text, xhr, elt) {
        return text; // Transform response HTML
    },
    isInlineSwap: function(swapStyle) {
        return false;
    },
    handleSwap: function(swapStyle, target, fragment, settleInfo) {
        return false; // Return true if handled
    },
    encodeParameters: function(xhr, parameters, elt) {
        return null; // Return encoded body or null
    }
});

// Remove extension
htmx.removeExtension('my-ext');

Configuration

Configure via JavaScript or <meta> tag.

JavaScript

htmx.config.defaultSwapStyle = 'outerHTML';
htmx.config.historyCacheSize = 20;

Meta Tag

<meta name="htmx-config" content='{
    "defaultSwapStyle": "outerHTML",
    "historyCacheSize": 20
}' />

All Configuration Options

OptionDefaultDescription
historyEnabledtrueEnable history snapshot and navigation
historyCacheSize10Max pages in history cache. 0 to disable
refreshOnHistoryMissfalseFull page reload on cache miss instead of AJAX
defaultSwapStyleinnerHTMLDefault hx-swap strategy
defaultSwapDelay0Default swap delay in ms
defaultSettleDelay20Default settle delay in ms
includeIndicatorStylestrueInject default indicator CSS
indicatorClasshtmx-indicatorClass for request indicators
requestClasshtmx-requestClass applied during requests
addedClasshtmx-addedClass for newly swapped content
settlingClasshtmx-settlingClass during settle phase
swappingClasshtmx-swappingClass during swap phase
allowEvaltrueAllow eval-dependent features (hx-on:, trigger filters, hx-vals with js:)
allowScriptTagstrueProcess <script> tags in responses
inlineScriptNonce''Nonce for inline scripts
inlineStyleNonce''Nonce for inline styles
attributesToSettle["class","style","width","height"]Attributes settled during settle phase
useTemplateFragmentsfalseUse <template> for HTML parsing (helps with table/SVG content)
wsReconnectDelayfull-jitterWebSocket reconnect delay strategy
wsBinaryTypeblobWebSocket binary data type
disableSelector[hx-disable], [data-hx-disable]CSS selector for disabled elements
withCredentialsfalseSend credentials with cross-origin requests
timeout0Request timeout in ms. 0 = no timeout
scrollBehaviorinstantScroll behavior: instant, smooth, auto
defaultFocusScrollfalseScroll focused element into view after swap
getCacheBusterParamfalseAdd cache-buster query param to GET requests
globalViewTransitionsfalseEnable View Transitions API globally
methodsThatUseUrlParams["get","delete"]Methods that encode params in URL
selfRequestsOnlytrueOnly allow same-origin requests
ignoreTitlefalseDon't update document.title from responses
disableInheritancefalseDisable attribute inheritance globally
scrollIntoViewOnBoosttrueScroll to top on boosted navigation
triggerSpecsCachenullCache object for parsed trigger specs
allowNestedOobSwapstrueProcess OOB swaps in nested content
responseHandlingSee docsArray of status code handling rules
historyRestoreAsHxRequesttrueSend HX-Request header on history restore
reportValidityOfFormsfalseReport validation errors via browser UI

Extensions

Installing Extensions

<!-- Via CDN -->
<script src="https://unpkg.com/htmx-ext-preload@2.1.0/preload.js"></script>

<!-- Enable on element or parent -->
<body hx-ext="preload">
    ...
</body>

Core Extensions

ExtensionPurposePackage
head-supportMerge <head> elements from responseshtmx-ext-head-support
htmx-1-compatHTMX 1.x compatibility layerhtmx-ext-htmx-1-compat
idiomorphDOM morphing swap strategyidiomorph
preloadPreload content on hover/focushtmx-ext-preload
response-targetsTarget different elements by HTTP status codehtmx-ext-response-targets
sseServer-Sent Events supporthtmx-ext-sse
wsWebSocket supporthtmx-ext-ws

SSE Extension

<div hx-ext="sse" sse-connect="/events">
    <!-- Swap content when 'message' event is received -->
    <div sse-swap="message">Waiting for messages...</div>

    <!-- Trigger HTMX request on SSE event -->
    <div hx-get="/update" hx-trigger="sse:notification">Notifications</div>
</div>

WebSocket Extension

<div hx-ext="ws" ws-connect="/ws">
    <!-- Send form data over WebSocket -->
    <form ws-send>
        <input name="message" />
        <button type="submit">Send</button>
    </form>

    <!-- Content area updated by WebSocket messages -->
    <div id="messages"></div>
</div>

response-targets Extension

Target different elements based on HTTP response status:

<div hx-ext="response-targets">
    <form hx-post="/submit"
          hx-target="#success"
          hx-target-422="#form-errors"
          hx-target-5*="#server-error">
        ...
    </form>
    <div id="success"></div>
    <div id="form-errors"></div>
    <div id="server-error"></div>
</div>

Disabling Extensions

<div hx-ext="preload">
    <!-- Preload enabled here -->
    <div hx-ext="ignore:preload">
        <!-- Preload disabled here -->
    </div>
</div>

Debugging

Log All Events

htmx.logAll();

// Disable all logging
htmx.logNone();

Custom Logger

htmx.logger = function(elt, event, data) {
    if (event === 'htmx:responseError') {
        console.error('HTMX Error:', data);
    }
};

Browser Console Monitoring

// Monitor all events on a specific element
monitorEvents(document.getElementById('my-element'));

Demo/Mock Server

Load https://demo.htmx.org in a <script> tag to mock server responses using <template> tags:

<script src="https://demo.htmx.org"></script>

<template url="/api/greeting" delay="500">
    <p>Hello, World!</p>
</template>

<button hx-get="/api/greeting" hx-target="#output">
    Get Greeting
</button>
<div id="output"></div>

Scripting Integration

hx-on: Inline Scripts

<!-- React to DOM events -->
<button hx-on:click="console.log('clicked')">Click</button>

<!-- React to HTMX events (kebab-case required) -->
<form hx-post="/submit"
      hx-on:htmx:before-request="showSpinner()"
      hx-on:htmx:after-request="hideSpinner()">
    ...
</form>

<!-- Access event detail -->
<div hx-get="/data"
     hx-on:htmx:config-request="event.detail.parameters.timestamp = Date.now()">
    Load
</div>

Recommended Scripting Approaches

ApproachUse Case
Vanilla JSSimple DOM manipulation, event handling
Alpine.jsLightweight client-side reactivity alongside HTMX
hyperscriptHTMX-team scripting language, _ attribute
jQueryLegacy compatibility

Alpine.js + HTMX Example

<div x-data="{ count: 0 }" hx-get="/data" hx-trigger="click">
    <span x-text="count"></span>
    <button @click="count++">Local increment</button>
</div>

Third-Party Library Integration

Initialize on New Content

htmx.onLoad(function(content) {
    // Initialize any library that needs DOM elements
    var charts = content.querySelectorAll('.chart');
    charts.forEach(function(el) {
        new Chart(el, JSON.parse(el.dataset.config));
    });
});

Clean Up Before Removal

document.addEventListener('htmx:beforeCleanupElement', function(event) {
    var chart = Chart.getChart(event.target);
    if (chart) {
        chart.destroy();
    }
});

Process Dynamically-Added HTMX

When adding HTML to the DOM outside of HTMX (e.g., via a third-party library):

var container = document.getElementById('dynamic');
container.innerHTML = '<button hx-get="/api/data">Load</button>';
htmx.process(container);

Event-Driven Integration

Libraries that fire DOM events can trigger HTMX requests:

<!-- Third-party fires 'map:click' on this element -->
<div id="map"
     hx-get="/location"
     hx-trigger="map:click"
     hx-target="#location-info">
</div>
<div id="location-info"></div>

references

attributes.md

events-api.md

extensions.md

gotchas.md

patterns.md

REFERENCE.md

requests.md

swapping.md

SKILL.md

tile.json