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

swapping.mdreferences/

description:
HTMX swap strategies, targets, OOB swaps, morphing, and transitions.
globs:
*.html

HTMX Swapping

How HTMX places response content into the DOM.

Contents

  • Swap Strategies
  • Swap Modifiers
  • Out-of-Band (OOB) Swaps
  • Morphing
  • CSS Transitions
  • View Transitions
  • Preserving Content

Swap Strategies

The hx-swap attribute controls how the response is inserted relative to the target element.

StrategyBehavior
innerHTMLDefault. Replace the target's inner content
outerHTMLReplace the entire target element (including itself)
textContentReplace the target's text content, without parsing HTML
beforebeginInsert before the target element (as a sibling)
afterbeginInsert inside the target, before its first child
beforeendInsert inside the target, after its last child
afterendInsert after the target element (as a sibling)
deleteDelete the target element, no content swap
noneNo content swap. OOB swaps and response headers still process
<!-- Replace inner content (default) -->
<div hx-get="/data" hx-swap="innerHTML">Loading...</div>

<!-- Replace the whole element -->
<div hx-get="/data" hx-swap="outerHTML">Replace me entirely</div>

<!-- Append to a list -->
<button hx-get="/more-items" hx-target="#list" hx-swap="beforeend">
    Load More
</button>

<!-- Delete on success -->
<button hx-delete="/item/1" hx-target="closest tr" hx-swap="delete">
    Remove
</button>

<!-- Fire-and-forget (no DOM update) -->
<button hx-post="/track" hx-swap="none">Track Click</button>

Note: Using outerHTML on <body> is automatically converted to innerHTML due to DOM limitations. hx-swap is inherited.

Swap Modifiers

Append modifiers to hx-swap separated by spaces. Each modifier uses key:value syntax.

<div hx-get="/data" hx-swap="innerHTML swap:300ms settle:100ms scroll:top">
    Content
</div>
ModifierEffectDefault
swap:<time>Delay between old content removal and new content insertion0
settle:<time>Delay between new content insertion and settle phase20ms
transition:trueUse the View Transitions API for this swapfalse
ignoreTitle:trueDon't update document.title from response <title>false
scroll:topScroll target element to its top after swap
scroll:bottomScroll target element to its bottom after swap
scroll:<selector>:topScroll specified element to top
scroll:<selector>:bottomScroll specified element to bottom
show:topScroll viewport so target's top is visible
show:bottomScroll viewport so target's bottom is visible
show:<selector>:topScroll viewport so specified element's top is visible
show:<selector>:bottomScroll viewport so specified element's bottom is visible
show:window:topScroll viewport to the top of the window
show:window:bottomScroll viewport to the bottom of the window
show:noneDisable all scroll-into-view behavior
focus-scroll:trueAuto-scroll to focused element after swapfalse

Scroll Examples

<!-- Scroll the target to top after loading -->
<div hx-get="/data" hx-swap="innerHTML scroll:top">Content</div>

<!-- Scroll viewport so #results top is visible -->
<button hx-get="/search" hx-target="#results" hx-swap="innerHTML show:#results:top">
    Search
</button>

<!-- Infinite scroll: append and show last item -->
<div hx-get="/page/2" hx-swap="beforeend show:bottom">...</div>

Out-of-Band (OOB) Swaps

OOB swaps update elements outside the target using their id attributes. This enables updating multiple page regions from a single response.

hx-swap-oob on Response Elements

The server response includes elements marked with hx-swap-oob:

<!-- Server response -->
<!-- This goes into the main target normally -->
<div>Main content here</div>

<!-- This swaps into #notification by its id, out of band -->
<div id="notification" hx-swap-oob="true">
    Item saved successfully!
</div>

<!-- OOB with specific strategy -->
<div id="item-count" hx-swap-oob="innerHTML">42</div>

<!-- OOB append -->
<tr id="table-body" hx-swap-oob="beforeend">
    <td>New row</td>
</tr>

hx-swap-oob Values

ValueBehavior
trueReplace the entire element by matching id (uses outerHTML)
innerHTMLReplace inner content of element with matching id
outerHTMLReplace entire element with matching id
beforebeginInsert before element with matching id
afterbeginPrepend inside element with matching id
beforeendAppend inside element with matching id
afterendInsert after element with matching id
deleteDelete element with matching id
noneDo nothing (suppresses OOB)

hx-select-oob on Requesting Elements

Pick specific elements from the response for OOB swapping:

<!-- The button: swap #results into target, and OOB swap #count and #status -->
<button hx-get="/data"
        hx-target="#results"
        hx-select="#results"
        hx-select-oob="#count:innerHTML, #status:outerHTML">
    Load
</button>

Format: #id:strategy, #id:strategy, ...

OOB Pattern: Update Multiple Regions

Server returns one response that updates several page areas:

<!-- Response from server -->
<div id="main-content">
    <h2>Updated Content</h2>
    <p>The primary content area.</p>
</div>

<div id="sidebar-count" hx-swap-oob="innerHTML">5 items</div>
<div id="notification" hx-swap-oob="afterbegin">
    <div class="alert">Content updated!</div>
</div>
<div id="last-updated" hx-swap-oob="innerHTML">Just now</div>

Morphing

Morphing merges new HTML into existing DOM, preserving focus, scroll position, and element state. Requires an extension.

Idiomorph (Recommended)

HTMX's own morphing algorithm. Matches elements by id and structure.

<head>
    <script src="https://unpkg.com/idiomorph@0.7.3/dist/idiomorph-ext.min.js"></script>
</head>
<body hx-ext="morph">
    <!-- Morph swap instead of replacing -->
    <div hx-get="/data" hx-swap="morph">Content</div>

    <!-- Morph just the inner content -->
    <div hx-get="/data" hx-swap="morph:innerHTML">Content</div>

    <!-- Morph the outer element -->
    <div hx-get="/data" hx-swap="morph:outerHTML">Content</div>
</body>

Other Morph Extensions

ExtensionPackageNotes
IdiomorphidiomorphHTMX team's recommended morph engine
Morphdom Swaphtmx-ext-morphdom-swapBased on the morphdom library
Alpine-morphhtmx-ext-alpine-morphIntegrates with Alpine.js morph plugin

When to Use Morphing

  • Forms with focus/cursor position that must be preserved
  • Lists with animations where elements reorder
  • Complex UIs where replacing breaks third-party widget state
  • Any case where full replacement causes visual flicker

CSS Transitions

HTMX works with CSS transitions when:

  1. The element has a stable id across requests
  2. The new content changes a CSS class or property
  3. CSS defines a transition for that property

How It Works

During swap, HTMX:

  1. Copies old element attributes to the new element
  2. Inserts new content with old values
  3. After one frame, applies new values
  4. CSS transition animates between old and new

Fade-Out on Swap

<style>
    .fade-me-out.htmx-swapping {
        opacity: 0;
        transition: opacity 1s ease-out;
    }
</style>

<!-- swap:1s gives CSS transition time to complete before removal -->
<button class="fade-me-out"
        hx-delete="/fade_out_demo"
        hx-swap="outerHTML swap:1s">
    Fade Me Out
</button>

Fade-In on Addition

<style>
    #fade-me-in.htmx-added {
        opacity: 0;
    }
    #fade-me-in {
        opacity: 1;
        transition: opacity 1s ease-out;
    }
</style>

<button id="fade-me-in"
        hx-post="/fade_in_demo"
        hx-swap="outerHTML settle:1s">
    Fade Me In
</button>

Request In-Flight Animation

<style>
    form.htmx-request {
        opacity: .5;
        transition: opacity 300ms linear;
    }
</style>

<form hx-post="/name" hx-swap="outerHTML">
    <label>Name:</label><input name="name" />
    <button type="submit">Submit</button>
</form>

Transition Classes

ClassApplied WhenRemoved When
htmx-requestRequest startsRequest ends
htmx-swappingBefore swapAfter swap
htmx-addedAfter swap (on new elements)After settle
htmx-settlingAfter swapAfter settle

View Transitions

The View Transitions API provides browser-native animated transitions.

Enable Per-Element

<div hx-get="/page" hx-swap="innerHTML transition:true">Content</div>

Enable Globally

htmx.config.globalViewTransitions = true;

Styling View Transitions

/* Customize the transition animation */
::view-transition-old(root) {
    animation: fade-out 0.2s ease-out;
}
::view-transition-new(root) {
    animation: fade-in 0.2s ease-in;
}

/* Named transitions for specific elements */
#content {
    view-transition-name: content;
}
::view-transition-old(content) {
    animation: slide-out 0.3s;
}
::view-transition-new(content) {
    animation: slide-in 0.3s;
}

Preventing View Transitions

Cancel via the htmx:beforeTransition event:

document.addEventListener('htmx:beforeTransition', function(event) {
    // Cancel transition based on condition
    if (shouldSkipTransition) {
        event.preventDefault();
    }
});

Preserving Content

hx-preserve

Keep an element completely unchanged across swaps. The element must have an id.

<div hx-get="/page" hx-select="#content" hx-target="#content">
    <div id="content">
        <video id="player" hx-preserve>
            <source src="video.mp4" />
        </video>
        <p>Other content that will update</p>
    </div>
</div>

Use cases:

  • Video/audio players that should keep playing
  • Iframes that should maintain state
  • Complex third-party widgets

HTML Element Encapsulation for OOB

Browsers enforce strict HTML structure. OOB swap elements may need wrapping:

Element TypeMust Be Wrapped In
<tr>, <td>, <th><tbody>, <table>, or <template>
<li><ul>, <ol>, <div>, <span>, or <template>
<p><div> or <span>
SVG elements<template> + <svg> (preserve XML namespace)
<template>
    <tr id="row-1" hx-swap-oob="true">
        <td>Updated cell</td>
    </tr>
</template>

references

attributes.md

events-api.md

extensions.md

gotchas.md

patterns.md

REFERENCE.md

requests.md

swapping.md

SKILL.md

tile.json