Htmx in a Nutshell

Htmx is a library that allows you to access modern browser features directly from HTML, rather than using javascript.

To understand htmx, first lets take a look at an anchor tag:

  <a href="/blog">Blog</a>

This anchor tag tells a browser:

"When a user clicks on this link, issue an HTTP GET request to '/blog' and load the response content into the browser window".

With that in mind, consider the following bit of HTML:

  <button hx-post="/clicked"
    Click Me!

This tells htmx:

"When a user clicks on this button, issue an HTTP POST request to '/clicked' and use the content from the response to replace the element with the id parent-div in the DOM"

Htmx extends and generalizes the core idea of HTML as a hypertext, opening up many more possibilities directly within the language:

  • Now any element, not just anchors and forms, can issue an HTTP request
  • Now any event, not just clicks or form submissions, can trigger requests
  • Now any HTTP verb, not just GET and POST, can be used
  • Now any element, not just the entire window, can be the target for update by the request

Note that when you are using htmx, on the server side you typically respond with HTML, not JSON. This keeps you firmly within the original web programming model, using Hypertext As The Engine Of Application State without even needing to really understand that concept.

It's worth mentioning that, if you prefer, you can use the data- prefix when using htmx:

  <a data-hx-post="/click">Click Me!</a>


Htmx is a dependency-free javascript library.

It can be used via NPM as "" or downloaded or included from unpkg or your other favorite NPM-based CDN:

    <script src=""></script>

For added security, you can load the script using Subresource Integrity (SRI).

    <script src="" integrity="sha384-QrlPmoLqMVfnV4lzjmvamY0Sv/Am8ca1W7veO++Sp6PiIGixqkD+0xZ955Nc03qO" crossorigin="anonymous"></script>


The core of htmx is a set of attributes that allow you to issue AJAX requests directly from HTML:

Attribute Description
hx-get Issues a GET request to the given URL
hx-post Issues a POST request to the given URL
hx-put Issues a PUT request to the given URL
hx-patch Issues a PATCH request to the given URL
hx-delete Issues a DELETE request to the given URL

Each of these attributes takes a URL to issue an AJAX request to. The element will issue a request of the specified type to the given URL when the element is triggered:

  <div hx-put="/messages">
    Put To Messages

This tells the browser:

When a user clicks on this div, issue a PUT request to the URL /messages and load the response into the div

Triggering Requests

By default, AJAX requests are triggered by the "natural" event of an element:

  • input, textarea & select are triggered on the change event
  • form is triggered on the submit event
  • everything else is triggered by the click event

If you want different behavior you can use the hx-trigger attribute to specify which event will cause the request.

Here is a div that posts to /mouse_entered when a mouse enters it:

   <div hx-post="/mouse_entered" hx-trigger="mouseenter">
      [Here Mouse, Mouse!]

Trigger Modifiers

A trigger can also have a few additional modifiers that change its behavior. For example, if you want a request to only happen once, you can use the once modifier for the trigger:

   <div hx-post="/mouse_entered" hx-trigger="mouseenter once">
     [Here Mouse, Mouse!]

Other modifiers you can use for triggers are:

  • changed - only issue a request if the value of the element has changed
  • delay:<time interval> - wait the given amount of time (e.g. 1s) before issuing the request. If the event triggers again, the countdown is reset.
  • throttle:<time interval> - wait the given amount of time (e.g. 1s) before issuing the request. Unlike delay if a new event occurs before the time limit is hit the event will be discarded, so the request will trigger at the end of the time period.
  • from:<CSS Selector> - listen for the event on a different element. This can be used for things like keyboard shortcuts.

You can use these attributes to implement many common UX patterns, such as Active Search:

   <input type="text" name="q"
          hx-trigger="keyup changed delay:500ms"
    <div id="search-results"></div>

This input will issue a request 500 milliseconds after a key up event if the input has been changed and inserts the results into the div with the id search-results.

Multiple triggers can be specified in the hx-trigger attribute, separated by commas.

Trigger Filters

You may also apply trigger filters by using square brackets after the event name, enclosing a javascript expression that will be evaluated. If the expression evaluates to true the event will trigger, otherwise it will not.

Here is an example that triggers only on a Control-Click of the element

<div hx-get="/clicked" hx-trigger="click[ctrlKey]">Control Click Me</div>

Properties like ctrlKey will be resolved against the triggering event first, then the global scope.

Special Events

htmx provides a few special events for use in hx-trigger:

  • load - fires once when the element is first loaded
  • revealed - fires once when an element first scrolls into the viewport

You can also use custom events to trigger requests if you have an advanced use case.


If you want an element to poll the given URL rather than wait for an event, you can use the every syntax with the hx-trigger attribute:

  <div hx-get="/news" hx-trigger="every 2s">

This tells htmx

Every 2 seconds, issue a GET to /news and load the response into the div

If you want to stop polling from a server response you can respond with the HTTP response code 286 and the element will cancel the polling.

Load Polling

Another technique that can be used to achieve polling in htmx is "load polling", where an element specifies an load trigger along with a delay, and replaces itself with the response:

<div hx-get="/messages"
     hx-trigger="load delay:1s"


If the /messages end point keeps returning a div set up this way, it will keep "polling" back to the URL every second.

Load polling can be useful in situations where a poll has an end point at which point the polling terminates, such as when you are showing the user a progress bar.

Request Indicators

When an AJAX request is issued it is often good to let the user know that something is happening since the browser will not give them any feedback. You can accomplish this in htmx by using htmx-indicator class.

The htmx-indicator class is defined so that the opacity of any element with this class is 0 by default, making it invisible but present in the DOM.

When htmx issues a request, it will put a htmx-request class onto an element (either the requesting element or another element, if specified). The htmx-request class will cause a child element with the htmx-indicator class on it to transition to an opacity of 1, showing the indicator.

  <button hx-get="/click">
      Click Me!
     <img class="htmx-indicator" src="/spinner.gif"/>

Here we have a button. When it is clicked the htmx-request class will be added to it, which will reveal the spinner gif element. (I like SVG spinners these days.)

While the htmx-indicator class uses opacity to hide and show the progress indicator, if you would prefer another mechanism you can create your own CSS transition like so:

    .htmx-request .my-indicator{

If you want the htmx-request class added to a different element, you can use the hx-indicator attribute with a CSS selector to do so:

      <button hx-get="/click" hx-indicator="#indicator">
        Click Me!
      <img id="indicator" class="htmx-indicator" src="/spinner.gif"/>

Here we call out the indicator explicitly by id. Note that we could have placed the class on the parent div as well and had the same effect.


If you want the response to be loaded into a different element other than the one that made the request, you can use the hx-target attribute, which takes a CSS selector. Looking back at our Live Search example:

   <input type="text" name="q"
          hx-trigger="keyup delay:500ms changed"
    <div id="search-results"></div>

You can see that the results from the search are going to be loaded into div#search-results, rather than into the input tag.


htmx offers a few different ways to swap the HTML returned into the DOM. By default, the content replaces the innerHTML of the target element. You can modify this by using the hx-swap attribute with any of the following values:

Name Description
innerHTML the default, puts the content inside the target element
outerHTML replaces the entire target element with the returned content
afterbegin prepends the content before the first child inside the target
beforebegin prepends the content before the target in the targets parent element
beforeend appends the content after the last child inside the target
afterend appends the content after the target in the targets parent element
none does not append content from response (out of band items will still be processed)

CSS Transitions

htmx makes it easy to use CSS Transitions without javascript. To understand how CSS transitions work in htmx, you must first understand the swap & settle model that htmx uses.

When new content is received from a server, before the content is swapped in, the existing content of the page is examined for elements that match by the id attribute. If a match is found for an element in the new content, the attributes of the old content are copied onto the new element before the swap occurs. The new content is then swapped in, but with the old attribute values. Finally, the new attribute values are swapped in, after a "settle" delay (100ms by default).

This may seem a bit complicated, but with this mechanic for swapping content, you can write CSS transitions from old to new attribute values.

An example will help clarify. Consider this original content:

  <div id="div1">Original Content</div>

And this content, which has been received by htmx after an AJAX request, to replace it:

  <div id="div1" class="red">New Content</div>

The first thing that htmx does, before it swaps in this new content, is note that these two elements match by id (and tag type). It therefore swaps the old attribute values onto the new content:

  <div id="div1">New Content</div>

Note that the new content no longer has a class attribute. This modified new content is then swapped into the DOM. This is the swap step.

Next, after a "settle" delay, the new div will have its attributes updated to the actual values received from the server:

  <div id="div1" class="red">New Content</div>

Because this div was in the DOM with the original div's attributes, this is will trigger a CSS transition. So you can write, for example, this CSS:

.red {
  color: red;
  transition: all ease-in 1s ;

And the newly swapped content will gently transition to a red text color over one second.

All of that may seem a little crazy, but it can be summarized as this:

In htmx, all you need to do to use CSS transitions for an element is keep its id stable across requests

Out of Band Swaps

If you want to swap content from a response directly into the DOM by using the id attribute you can use the hx-swap-oob attribute in the response html:

  <div id="message" hx-swap-oob="true">Swap me directly!</div>
  Additional Content

In this response, div#message would be swapped directly into the matching DOM element, while the additional content would be swapped into the target in the normal manner.

You can use this technique to "piggy-back" updates on other requests.

Note that out of band elements must be in the top level of the response, and not children of the top level elements.

Selecting Content To Swap

If you want to select a subset of the response HTML to swap into the target, you can use the hx-select attribute, which takes a CSS selector and selects the matching elements from the response.

Preserving Content During A Swap

If there is content that you wish to be preserved across swaps (e.g. a video player that you wish to remain playing even if a swap occurs) you can use the hx-preserve attribute on the elements you wish to be preserved.


By default, an element that causes a request will include its value if it has one. If the element is a form it will include the values of all inputs within it.

Additionally, if the element causes a non-GET request, the values of all the inputs of the nearest enclosing form will be included.

If you wish to include the values of other elements, you can use the hx-include attribute with a CSS selector of all the elements whose values you want to include in the request.

If you wish to filter out some parameters you can use the hx-params attribute.

Finally, if you want to programatically modify the parameters, you can use the htmx:configRequest event.

File Upload

If you wish to upload files via an htmx request, you can set the hx-encoding attribute to multipart/form-data. This will use a FormData object to submit the request, which will properly include the file in the request.

Note that depending on your server-side technology, you may have to handle requests with this type of body content very differently.

Note that htmx fires a htmx:xhr:progress event periodically based on the standard progress event during upload, which you can hook into to show the progress of the upload.

Extra Values

You can include extra values in a request using the hx-vals (name-expression pairs in JSON format) and hx-vars attributes (comma-separated name-expression pairs that are dynamically computed).


Htmx supports "boosting" regular HTML anchors and forms with the hx-boost attribute. This attribute will convert all anchor tags and forms into AJAX requests that, by default, target the body of the page.

Here is an example:

<div hx-boost="true">
    <a href="/blog">Blog</a>

The anchor tag in this div will issue an AJAX GET request to /blog and swap the response into the body tag.

This functionality is somewhat similar to Turbolinks and allows you to use htmx for progressive enhancement.

Web Sockets & SSE

Htmx has experimental support for declarative use of both WebSockets and Server Sent Events.

These features are under active development, so please let us know if you are willing to experiment with them.


If you wish to establish a WebSocket connection in htmx, you use the hx-ws attribute:

  <div hx-ws="connect:wss:/chatroom">
    <div id="chat_room">
    <form hx-ws="send:submit">
        <input name="chat_message">

The source declaration established the connection, and the send declaration tells the form to submit values to the socket on submit.

More details can be found on the hx-ws attribute page

Server Sent Events

Server Sent Events are a way for servers to send events to browsers. It provides a higher-level mechanism for communication between the server and the browser than websockets.

If you want an element to respond to a Server Sent Event via htmx, you need to do two things:

  1. Define an SSE source. To do this, add a hx-sse attribute on a parent element with a connect <url> declaration that specifies the URL from which Server Sent Events will be received.

  2. Define elements that are descendents of this element that are triggered by server sent events using the hx-trigger="sse:<event_name>" syntax

Here is an example:

    <body hx-sse="connect:/news_updates">
        <div hx-trigger="sse:new_news" hx-get="/news"></div>

Depending on your implementation, this may be more efficient than the polling example above since the server would notify the div if there was new news to get, rather than the steady requests that a poll causes.

History Support

Htmx provides a simple mechanism for interacting with the browser history API:

If you want a given element to push its request URL into the browser navigation bar and add the current state of the page to the browser's history, include the hx-push-url attribute:

    <a hx-get="/blog" hx-push-url="true">Blog</a>

When a user clicks on this link, htmx will snapshot the current DOM and store it before it makes a request to /blog. It then does the swap and pushes a new location onto the history stack.

When a user hits the back button, htmx will retrieve the old content from storage and swap it back into the target, simulating "going back" to the previous state. If the location is not found in the cache, htmx will make an ajax request to the given URL, with the header HX-History-Restore-Request set to true, and expects back the HTML needed for the entire page. Alternatively, if the htmx.config.refreshOnHistoryMiss config variable is set to true, it will issue a hard browser refresh.

NOTE: If you push a URL into the history, you must be able to navigate to that URL and get a full page back! A user could copy and paste the URL into an email, or new tab. Additionally, htmx will need the entire page when restoring history if the page is not in the history cache.

Specifying History Snapshot Element

By default, htmx will use the body to take and restore the history snapshot from. This is usually the right thing, but if you want to use a narrower element for snapshotting you can use the hx-history-elt attribute to specify a different one.

Careful: this element will need to be on all pages or restoring from history won't work reliably.

Requests & Responses

Htmx expects responses to the AJAX requests it makes to be HTML, typically HTML fragments (although a full HTML document, matched with a hx-select tag can be useful too). Htmx will then swap the returned HTML into the document at the target specified and with the swap strategy specified.

Sometimes you might want to do nothing in the swap, but still perhaps trigger a client side event (see below). For this situation you can return a 204 - No Content response code, and htmx will ignore the content of the response.

In the event of an error response from the server (e.g. a 404 or a 501), htmx will trigger the htmx:responseError event, which you can handle.

In the event of a connection error, the htmx:sendError event will be triggered.

Request Headers

htmx includes a number of useful headers in requests:

Header Description
HX-Request will be set to "true"
HX-Trigger will be set to the id of the element that triggered the request
HX-Trigger-Name will be set to the name of the element that triggered the request
HX-Target will be set to the id of the target element
HX-Prompt will be set to the value entered by the user when prompted via hx-prompt

Response Headers

htmx supports some htmx-specific response headers:

  • HX-Push - pushes a new URL into the browser’s address bar
  • HX-Redirect - triggers a client-side redirect to a new location
  • HX-Refresh - if set to "true" the client side will do a full refresh of the page
  • HX-Trigger - triggers client side events
  • HX-Trigger-After-Swap - triggers client side events after the swap step
  • HX-Trigger-After-Settle - triggers client side events after the settle step

For more on the HX-Trigger headers, see HX-Trigger Response Headers.

Request Order of Operations

The order of operations in a htmx request are:

  • The element is triggered and begins a request
    • Values are gathered for the request
    • The htmx-request class is applied to the appropriate elements
    • The request is then issued asynchronously via AJAX
      • Upon getting a response the target element is marked with the htmx-swapping class
      • An optional swap delay is applied (see the hx-swap attribute)
      • The actual content swap is done
        • the htmx-swapping class is removed from the target
        • the htmx-settling class is applied to the target
        • A settle delay is done (default: 100ms)
        • The DOM is settled
        • the htmx-settling class is removed from the target

You can use the htmx-swapping and htmx-settling classes to create CSS transitions between pages.


Htmx integrates with the HTML5 Validation API and will not issue a request if a validatable input is invalid. This is true for both AJAX requests as well as WebSocket sends.

Htmx fires events around validation that can be used to hook in custom validation and error handling:

  • htmx:validation:validate - called before an elements checkValidity() method is called. May be used to add in custom validation logic
  • htmx:validation:failed - called when checkValidity() returns false, indicating an invalid input
  • htmx:validation:halted - called when a request is not issued due to validation errors. Specific errors may be found in the event.details.errors object

Validation Example

Here is an example of an input that uses the htmx:validation:validate event to require that an input have the value foo, using hyperscript:

<form hx-post="/test">
  <input _="on htmx:validation:validate
               if my.value != 'foo'
                  call me.setCustomValidity('Please enter the value foo')
                  call me.setCustomValidity('')"

Note that all client side validations must be re-done on the server side, as they can always be bypassed.


Htmx allows you to use CSS transitions in many situations using only HTML and CSS.

Please see the Animation Guide for more details on the options available.


Htmx has an extension mechanism that allows you to customize the libraries' behavior. Extensions are defined in javascript and then used via the hx-ext attribute:

<div hx-ext="debug">
  <button hx-post="/example">This button used the debug extension</button>
  <button hx-post="/example" hx-ext="ignore:debug">This button does not</button>

If you are interested in adding your own extension to htmx, please see the extension docs

Included Extensions

Htmx includes some extensions that are tested against the htmx code base. Here are a few:

Extension Description
json-enc use JSON encoding in the body of requests, rather than the default x-www-form-urlencoded
morphdom-swap an extension for using the morphdom library as the swapping mechanism in htmx.
client-side-templates support for client side template processing of JSON responses
path-deps an extension for expressing path-based dependencies similar to intercoolerjs
class-tools an extension for manipulating timed addition and removal of classes on HTML elements

See the extensions page for a complete list.

Events & Logging

Htmx has an extensive events mechanism, which doubles as the logging system.

If you want to register for a given htmx event you can use the following javascript:

  htmx.on("htmx:load", function(evt) {

This event is fired every time an element is loaded into the DOM by htmx, and is effectively the load event. In fact this is so common, you can use the helper function:

  htmx.onLoad(function(target) {

This does the same thing as the first example, but is a little cleaner.

The full set of events can be seen on the reference page.

Note that all events are fired with two different names

  • Camel Case
  • Kebab Case

So, for example, you can listen for htmx:afterSwap or for htmx:after-swap. This facilitates interoperability with libraries like Alpine.js.


If you set a logger at htmx.logger, every event will be logged. This can be very useful for troubleshooting:

    htmx.logger = function(elt, event, data) {
        if(console) {
            console.log(event, elt, data);


Declarative and event driven programming with htmx (or any other declartive language) can be a wonderful and highly productive activity, but one disadvantage when compared with imperative approaches is that it can be tricker to debug.

Figuring out why something isn't happening, for example, can be difficult if you don't know the tricks.

Well, here are the tricks:

The first debugging tool you can use is the htmx.logAll() method. This will log every event that htmx triggers and will allow you to see exactly what the library is doing.


Of course, that won't tell you why htmx isn't doing something. You might also not know what events a DOM element is firing to use as a trigger. To address this, you can use the monitorEvents() method available in the browser console:


This will spit out all events that are occuring on the element with the id theElement to the console, and allow you to see exactly what is going on with it.

Note that this only works from the console, you cannot embed it in a script tag on your page.

Finally, push come shove, you might want to just debug htmx.js by loading up the unminimized version. It's about 2500 lines of javascript, so not an insurmountable amount of code. You would most likely want to set a break point in the issueAjaxRequest() and handleAjaxResponse() methods to see what's going on.

And always feel free to jump on the Discord if you need help.


Hyperscript is an experimental front end scripting language designed to be expressive and easily embeddable directly in HTML for handling custom events, etc. The language is inspired by HyperTalk, javascript, gosu and others.

You can explore the language more fully on its main website:

Hyperscript is not required when using htmx, anything you can do in hyperscript can be done in vanilla JS or with another javascript library like jQuery, but the two technologies were designed with one another in mind and play well together.

Events & Hyperscript

Hyperscript was designed to help address features and functionality from intercooler.js that are not implemented in htmx directly, in a more flexible and open manner. One of its prime features is the ability to respond to arbitrary events on a DOM element, using the on syntax:

<div _="on htmx:afterSettle log 'Settled!'">

This will log Settled! to the console when the htmx:afterSettle event is triggered.

intercooler.js features & hyperscript implementations

Below are some examples of intercooler features and the hyperscript equivalent.


Intercooler provided the ic-remove-after attribute for removing an element after a given amount of time.

In hyperscript you can implement this, as well as fade effect, like so:

<div _="on load wait 5s then transition opacity to 0 then remove me">
  Here is a temporary message!

Intercooler provided the ic-post-errors-to attribute for posting errors that occured during requests and responses.

In hyperscript similar functionality is implemented like so:

<body _="on htmx:error(errorInfo) fetch /errors {method:'POST', body:{errorInfo:errorInfo} as JSON} ">

Intercooler provided the ic-switch-class attribute, which let you switch a class between siblings.

In hyperscript you can implement similar functionality like so:

<div hx-target="#content" _="on htmx:beforeOnLoad take .active from .tabs for">
    <a class="tabs active" hx-get="/tabl1" >Tab 1</a>
    <a class="tabs" hx-get="/tabl2">Tab 2</a>
    <a class="tabs" hx-get="/tabl3">Tab 3</a>
<div id="content">Tab 1 Content</div>

3rd Party Javascript

Htmx integrates fairly well with third party libraries. If the library fires events on the DOM, you can use those events to trigger requests from htmx.

A good example of this is the SortableJS demo:

<form class="sortable" hx-post="/items" hx-trigger="end">
  <div class="htmx-indicator">Updating...</div>
  <div><input type='hidden' name='item' value='1'/>Item 1</div>
  <div><input type='hidden' name='item' value='2'/>Item 2</div>
  <div><input type='hidden' name='item' value='2'/>Item 3</div>

With Sortable, as with most javascript libraries, you need to initialize content at some point.

In jquery you might do this like so:

$(document).ready(function() {
    var sortables = document.body.querySelectorAll(".sortable");
    for (var i = 0; i < sortables.length; i++) {
      var sortable = sortables[i];
      new Sortable(sortable, {
          animation: 150,
          ghostClass: 'blue-background-class'

In htmx, you would instead use the htmx.onLoad function, and you would select only from the newly loaded content, rather than the entire document:

htmx.onLoad(function(content) {
    var sortables = content.querySelectorAll(".sortable");
    for (var i = 0; i < sortables.length; i++) {
      var sortable = sortables[i];
      new Sortable(sortable, {
          animation: 150,
          ghostClass: 'blue-background-class'

This will ensure that as new content is added to the DOM by htmx, sortable elements are properly initialized.

If javascript adds content to the DOM that has htmx attributes on it, you need to make sure that this content is initialized with the htmx.process() function.

For example, if you were to fetch some data and put it into a div using the fetch API, and that HTML had htmx attributes in it, you would need to add a call to htmx.process() like this:

  let myDiv = document.getElementById('my-div')
    .then(response => response.text())
    .then(data => { myDiv.innerHTML = data; htmx.process(myDiv); } );


htmx allows you to define logic directly in your DOM. This has a number of advantages, the largest being Locality of Behavior making your system more coherent.

One concern with this approach, however, is security. This is especially the case if you are injecting user-created content into your site without any sort of HTML escaping discipline.

You should, of course, escape all 3rd party untrusted content that is injected into your site to prevent, among other issues, XSS attacks. Attributes starting with hx- and data-hx, as well as inline <script> tags should be filtered.

It is important to understand that htmx does not require inline scripts or eval() for most of its features. You (or your security team) may use a CSP that intentionally disallows inline scripts and the use of eval(). This, however, will have no effect on htmx functionality, which will still be able to execute JavaScript code placed in htmx attributes and may be a security concern.

To address this, if you don't want a particular part of the DOM to allow for htmx functionality, you can place the hx-disable or data-hx-disable attribute on the enclosing element of that area.

This will prevent htmx from executing within that area in the DOM:

  <div hx-disable>
    <%= user_content %>

This approach allows you to enjoy the benefits of Locality of Behavior while still providing additional safety if your HTML-escaping discipline fails.

Configuring htmx

Htmx has some configuration options that can be accessed either programatically or declaratively. They are listed below:

Config Variable Info
htmx.config.historyEnabled defaults to true, really only useful for testing
htmx.config.historyCacheSize defaults to 10
htmx.config.refreshOnHistoryMiss defaults to false, if set to true htmx will issue a full page refresh on history misses rather than use an AJAX request
htmx.config.defaultSwapStyle defaults to innerHTML
htmx.config.defaultSwapDelay defaults to 0
htmx.config.defaultSettleDelay defaults to 100
htmx.config.includeIndicatorStyles defaults to true (determines if the indicator styles are loaded)
htmx.config.indicatorClass defaults to htmx-indicator
htmx.config.requestClass defaults to htmx-request
htmx.config.settlingClass defaults to htmx-settling
htmx.config.swappingClass defaults to htmx-swapping
htmx.config.allowEval defaults to true
htmx.config.wsReconnectDelay defaults to full-jitter
htmx.config.disableSelector defaults to [disable-htmx], [data-disable-htmx], htmx will not process elements with this attribute on it or a parent

You can set them directly in javascript, or you can use a meta tag:

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


And that's it! Have fun with htmx: you can accomplish quite a bit without a lot of code.