---
title: "Documentation"
description: "From installation to advanced usage."
---
import { Code } from 'astro:components';
import integrity from '../data/integrity.json';
import { codeBlockTransformer } from '../lib/shiki-transformers.js';

<hr data-sidebar-group="Get Started" />

## Installation

htmx is a single JavaScript file with no dependencies. No build step is required to use it.

### CDN

Add this in your `<head>` tag:

<Code lang="html" code={`<script src="https://cdn.jsdelivr.net/npm/htmx.org@${integrity.version}" integrity="${integrity.min}" crossorigin="anonymous"></script>`} theme="css-variables" transformers={[codeBlockTransformer]} />

#### Unminified

<Code lang="html" code={`<script src="https://cdn.jsdelivr.net/npm/htmx.org@${integrity.version}/dist/htmx.js" integrity="${integrity.full}" crossorigin="anonymous"></script>`} theme="css-variables" transformers={[codeBlockTransformer]} />

#### ES Module

<Code lang="html" code={`<script type="module" src="https://cdn.jsdelivr.net/npm/htmx.org@${integrity.version}/dist/htmx.esm.min.js" integrity="${integrity.esmMin}" crossorigin="anonymous"></script>`} theme="css-variables" transformers={[codeBlockTransformer]} />

#### ES Module (unminified)

<Code lang="html" code={`<script type="module" src="https://cdn.jsdelivr.net/npm/htmx.org@${integrity.version}/dist/htmx.esm.js" integrity="${integrity.esm}" crossorigin="anonymous"></script>`} theme="css-variables" transformers={[codeBlockTransformer]} />

### Download

Instead of using a CDN, consider [self-hosting in production](https://blog.wesleyac.com/posts/why-not-javascript-cdn).

1. Download <a download href="https://cdn.jsdelivr.net/npm/htmx.org@__VERSION__/dist/htmx.min.js">htmx.min.js</a>
2. Save it to your project (e.g., `/js/htmx.min.js`)
3. Add this in your `<head>` tag:

```html
<script src="/js/htmx.min.js"></script>
```

#### Other formats

Download: <a download href="https://cdn.jsdelivr.net/npm/htmx.org@__VERSION__/dist/htmx.js">htmx.js</a> (unminified)

Download: <a download href="https://cdn.jsdelivr.net/npm/htmx.org@__VERSION__/dist/htmx.esm.min.js">htmx.esm.min.js</a> (ES module)

Download: <a download href="https://cdn.jsdelivr.net/npm/htmx.org@__VERSION__/dist/htmx.esm.js">htmx.esm.js</a> (ES module, unminified)

### npm

```sh
npm install htmx.org@__VERSION__
```

```javascript
import 'htmx.org';
```

#### Named import

```javascript
import htmx from 'htmx.org';

// Now you can use htmx.ajax(), htmx.find(), etc.
```

### htmax

The `htmax.js` file bundles htmx with the most popular extensions in a single file:

* [`hx-sse`](/extensions/hx-sse)
* [`hx-ws`](/extensions/hx-ws)
* [`hx-preload`](/extensions/hx-preload)
* [`hx-browser-indicator`](/extensions/hx-browser-indicator)
* [`hx-download`](/extensions/hx-download)
* [`hx-optimistic`](/extensions/hx-optimistic)
* [`hx-targets`](/extensions/hx-targets)
* [`hx-live`](/extensions/hx-live)
* [`hx-upsert`](/extensions/hx-upsert)
* [`hx-alpine-compat`](/extensions/hx-alpine-compat)
* [`hx-history-cache`](/extensions/hx-history-cache) _(included but disabled by default)_

The extensions are automatically available, you can just use their attributes directly (e.g. `hx-sse:connect`, `hx-ws:connect`).

```html
<script src="/js/htmax.min.js"></script>
```

To enable history caching, opt in via the meta tag:

```html
<meta name="htmx-config" content='{"historyCache": {"disable": false}}'>
```

## Migration

There are two major behavioral changes between htmx 2.x and 4.x:

* In htmx 2.0 attribute inheritance is *implicit* by default while in 4.0 it is explicit by default
* In htmx 2.0, `400` and `500` response codes are not swapped by default, whereas in htmx 4.0 these requests will be
  swapped

Add these two config lines to restore htmx 2.x behavior:

```html

<script>
    htmx.config.implicitInheritance = true;
    htmx.config.noSwap = [204, 304, '4xx', '5xx'];
</script>
<script src="https://cdn.jsdelivr.net/npm/htmx.org@__VERSION__/dist/htmx.min.js"></script>
```

[`implicitInheritance`](/reference/config/htmx-config-implicitInheritance) restores htmx 2's implicit attribute
inheritance. [`noSwap`](/reference/config/htmx-config-noSwap) prevents swapping error responses.

Or load the [`htmx-2-compat`](/extensions/htmx-2-compat) extension, which restores implicit inheritance, old event
names, and previous error-swapping defaults:

```html

<script src="/path/to/htmx.js"></script>
<script src="/path/to/ext/htmx-2-compat.js"></script>
```

Most htmx 2 apps should work with either approach. Then migrate incrementally using this guide.

### Upgrade Checker

htmx 4 ships with a command-line tool scans your templates and JS files for htmx 2 code that needs updating. It checks 
for removed attributes, old event names, inheritance patterns, extension changes, etc.

```bash
npx htmx.org@__VERSION__ upgrade-check -- ./path/to/project/root

npx htmx.org@__VERSION__ upgrade-check --ext .vue ./path/to/project/root
```

By default, the tool scans `.html`, `.php`, `.js`, `.ts`, `.jinja`, `.jinja2`, `.j2`, `.erb`, and `.hbs` files.

Output is `file:line` format, clickable in most editors. You can add additional file types with the `--ext` option.

The tool requires Python 3.

### What Changed

#### `fetch()` replaces `XMLHttpRequest`

All requests use the native [`fetch()` API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API). This cannot be
reverted.

#### Explicit inheritance

Add [`:inherited`](#attribute-inheritance) to any attribute that should inherit down the DOM tree.

```html
<!-- htmx 2: implicit inheritance -->
<div hx-confirm="Are you sure?">
    <button hx-delete="/item/1">Delete</button>
</div>

<!-- htmx 4: explicit inheritance -->
<div hx-confirm:inherited="Are you sure?">
    <button hx-delete="/item/1">Delete</button>
</div>
```

Works on any attribute: [`hx-boost`](/reference/attributes/hx-boost)`:inherited`, [
`hx-target`](/reference/attributes/hx-target)`:inherited`, [`hx-confirm`](/reference/attributes/hx-confirm)`:inherited`,
etc.

Use `:append` to add to an inherited value instead of replacing it:

```html

<div hx-include:inherited="#global-fields">
    <!-- appends .extra to the inherited value -->
    <form hx-include:inherited:append=".extra">...</form>
</div>
```

Revert: [`htmx.config.implicitInheritance`](/reference/config/htmx-config-implicitInheritance) `= true`

#### Error responses swap

htmx 4 swaps all HTTP responses. Only [`204`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/204)
and [`304`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/304) do not swap.

htmx 2 did not swap `4xx` and `5xx` responses. In htmx 4, if your server returns HTML with a `422` or `500`, that HTML
gets swapped into the target. Design your error responses to work as swap content, or use [
`hx-status`](/reference/attributes/hx-status) to control per-code behavior.

Revert: [`htmx.config.noSwap`](/reference/config/htmx-config-noSwap) `= [204, 304, '4xx', '5xx']`

#### [`hx-delete`](/reference/attributes/hx-delete) excludes form data

Like [`hx-get`](/reference/attributes/hx-get), [`hx-delete`](/reference/attributes/hx-delete) no longer includes the
enclosing form's inputs.

Fix: add [`hx-include`](/reference/attributes/hx-include)`="closest form"` where needed.

#### No history cache

History no longer caches pages in [localStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage).
When navigating back, htmx re-fetches the page and swaps it into `<body>`, or into the `[hx-history-elt]` element if
one is present, the same behavior as htmx 2.

Use [`htmx.config.history`](/reference/config/htmx-config-history) `= "reload"` for a full page reload instead. Use
`htmx.config.history = false` to disable.

#### OOB swap order

In htmx 2, out-of-band ([`hx-swap-oob`](/reference/attributes/hx-swap-oob)) elements swapped **before** the main
content.

In htmx 4, the main content swaps first. OOB and [`<hx-partial>`](#partials-hx-partial) elements swap after (in document order).

This matters if an OOB swap creates or modifies DOM that the main swap depends on. If your app relies on that ordering,
restructure so each swap is independent.

#### `hx-trigger` `queue` modifier removed

The `queue` modifier on [`hx-trigger`](/reference/attributes/hx-trigger) (e.g. `hx-trigger="click queue:all"`) no longer
works. Request queuing is now controlled exclusively by [`hx-sync`](/reference/attributes/hx-sync).

```html
<!-- htmx 2 -->
<div hx-trigger="click queue:all" hx-get="/test">...</div>

<!-- htmx 4: use hx-sync instead -->
<div hx-trigger="click" hx-get="/test" hx-sync="this:queue all">...</div>
```

#### 60-second timeout

htmx 2 had no timeout (`0`). htmx 4 sets [`defaultTimeout`](/reference/config/htmx-config-defaultTimeout) to `60000`.

Revert: `htmx.config.defaultTimeout = 0`

#### Extension loading

Include extension scripts directly. No attribute needed:

```html

<script src="/path/to/htmx.js"></script>
<script src="/path/to/ext/sse.js"></script>
```

Restrict which extensions can load:

```html

<meta name="htmx-config" content='{"extensions": "sse, ws"}'>
```

Extension authors use `htmx.registerExtension(name, methodMap)` to register.

See [Extension System](#extension-system) for details.

### Renames and Removals

#### Attributes

Do the `hx-disable` rename **before** upgrading. In htmx 2, `hx-disable` meant "skip htmx processing on this element". In htmx 4, that role is [`hx-ignore`](/reference/attributes/hx-ignore), and `hx-disable` now does what `hx-disabled-elt` used to do.

| htmx 2.x          | htmx 4.x                                                                                           | Type    | Notes                                                                                              |
|-------------------|----------------------------------------------------------------------------------------------------|---------|----------------------------------------------------------------------------------------------------|
| [`hx-disable`](https://htmx.org/attributes/hx-disable/)           | [`hx-ignore`](/reference/attributes/hx-ignore)                                                     | renamed | Rename this first to avoid conflicts.                                                              |
| [`hx-disabled-elt`](https://htmx.org/attributes/hx-disabled-elt/) | [`hx-disable`](/reference/attributes/hx-disable)                                                   | renamed | Rename this after `hx-disable` becomes `hx-ignore`.                                                |
| [`hx-vars`](https://htmx.org/attributes/hx-vars/)                 | [`hx-vals`](/reference/attributes/hx-vals)                                                         | removed | Use the `js:` prefix.                                                                              |
| [`hx-params`](https://htmx.org/attributes/hx-params/)             | [`htmx:config:request`](/reference/events/htmx-config-request)                                     | removed | Configure request parameters in the event.                                                         |
| [`hx-prompt`](https://htmx.org/attributes/hx-prompt/)             | [`hx-prompt` extension](/extensions/hx-prompt)                                                     | removed | You can also use a [one-liner with `hx-on`](/extensions/hx-prompt#without-the-extension).          |
| [`hx-ext`](https://htmx.org/attributes/hx-ext/)                   | —                                                                                                  | removed | [Include extension scripts directly](#extension-system).                                           |
| [`hx-disinherit`](https://htmx.org/attributes/hx-disinherit/)     | —                                                                                                  | removed | Not needed. Inheritance is explicit.                                                               |
| [`hx-inherit`](https://htmx.org/attributes/hx-inherit/)           | —                                                                                                  | removed | Not needed. Inheritance is explicit.                                                               |
| [`hx-request`](https://htmx.org/attributes/hx-request/)           | [`hx-config`](/reference/attributes/hx-config)                                                     | removed | —                                                                                                  |
| [`hx-history`](https://htmx.org/attributes/hx-history/)           | —                                                                                                  | removed | No [localStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage).           |

#### Events

All events follow a new pattern: `htmx:phase:action[:sub-action]`. Most error events are consolidated to [`htmx:error`](/reference/events/htmx-error). HTTP error responses use [`htmx:response:error`](/reference/events/htmx-response-error).

| htmx 2.x                    | htmx 4.x                                                                          | Type    | Notes                               |
|-----------------------------|-----------------------------------------------------------------------------------|---------|-------------------------------------|
| `htmx:afterOnLoad`          | [`htmx:after:init`](/reference/events/htmx-after-init)                            | renamed | —                                   |
| `htmx:afterProcessNode`     | [`htmx:after:init`](/reference/events/htmx-after-init)                            | renamed | —                                   |
| `htmx:afterRequest`         | [`htmx:after:request`](/reference/events/htmx-after-request)                      | renamed | —                                   |
| `htmx:afterSettle`          | [`htmx:after:settle`](/reference/events/htmx-after-settle)                        | renamed | —                                   |
| `htmx:afterSwap`            | [`htmx:after:swap`](/reference/events/htmx-after-swap)                            | renamed | —                                   |
| `htmx:beforeCleanupElement` | [`htmx:before:cleanup`](/reference/events/htmx-before-cleanup)                    | renamed | —                                   |
| `htmx:beforeHistorySave`    | [`htmx:before:history:update`](/reference/events/htmx-before-history-update)      | renamed | —                                   |
| `htmx:beforeOnLoad`         | [`htmx:before:init`](/reference/events/htmx-before-init)                          | renamed | —                                   |
| `htmx:beforeProcessNode`    | [`htmx:before:process`](/reference/events/htmx-before-process)                    | renamed | —                                   |
| `htmx:beforeRequest`        | [`htmx:before:request`](/reference/events/htmx-before-request)                    | renamed | —                                   |
| `htmx:beforeSwap`           | [`htmx:before:swap`](/reference/events/htmx-before-swap)                          | renamed | —                                   |
| `htmx:configRequest`        | [`htmx:config:request`](/reference/events/htmx-config-request)                    | renamed | —                                   |
| `htmx:historyCacheMiss`     | [`htmx:before:history:restore`](/reference/events/htmx-before-restore-history)    | renamed | —                                   |
| `htmx:historyRestore`       | [`htmx:before:history:restore`](/reference/events/htmx-before-restore-history)    | renamed | —                                   |
| `htmx:load`                 | [`htmx:after:init`](/reference/events/htmx-after-init)                            | renamed | —                                   |
| `htmx:oobAfterSwap`         | [`htmx:after:swap`](/reference/events/htmx-after-swap)                            | renamed | —                                   |
| `htmx:oobBeforeSwap`        | [`htmx:before:swap`](/reference/events/htmx-before-swap)                          | renamed | —                                   |
| `htmx:pushedIntoHistory`    | [`htmx:after:history:push`](/reference/events/htmx-after-push-into-history)       | renamed | —                                   |
| `htmx:replacedInHistory`    | [`htmx:after:history:replace`](/reference/events/htmx-after-replace-into-history) | renamed | —                                   |
| `htmx:responseError`        | [`htmx:response:error`](/reference/events/htmx-response-error)                    | renamed | HTTP error responses use this event. |
| `htmx:sendError`            | [`htmx:error`](/reference/events/htmx-error)                                      | renamed | —                                   |
| `htmx:swapError`            | [`htmx:error`](/reference/events/htmx-error)                                      | renamed | —                                   |
| `htmx:targetError`          | [`htmx:error`](/reference/events/htmx-error)                                      | renamed | —                                   |
| `htmx:timeout`              | [`htmx:error`](/reference/events/htmx-error)                                      | renamed | —                                   |
| `htmx:validation:validate`  | —                                                                                 | removed | Use native browser form validation.  |
| `htmx:validation:failed`    | —                                                                                 | removed | Use native browser form validation.  |
| `htmx:validation:halted`    | —                                                                                 | removed | Use native browser form validation.  |
| `htmx:xhr:loadstart`        | —                                                                                 | removed | htmx uses `fetch()` now.             |
| `htmx:xhr:loadend`          | [`htmx:finally:request`](/reference/events/htmx-finally-request)                  | removed | htmx uses `fetch()` now.             |
| `htmx:xhr:progress`         | —                                                                                 | removed | htmx uses `fetch()` now.             |
| `htmx:xhr:abort`            | [`htmx:error`](/reference/events/htmx-error)                                      | removed | htmx uses `fetch()` now.             |

#### Config

| htmx 2.x                 | htmx 4.x                                                                   | Type            | Notes                                                                                                                                                |
|--------------------------|----------------------------------------------------------------------------|-----------------|------------------------------------------------------------------------------------------------------------------------------------------------------|
| `defaultSwapStyle`       | [`defaultSwap`](/reference/config/htmx-config-defaultSwap)                 | renamed         | —                                                                                                                                                    |
| `globalViewTransitions`  | [`transitions`](/reference/config/htmx-config-transitions)                 | renamed         | —                                                                                                                                                    |
| `historyEnabled`         | [`history`](/reference/config/htmx-config-history)                         | renamed         | —                                                                                                                                                    |
| `includeIndicatorStyles` | [`includeIndicatorCSS`](/reference/config/htmx-config-includeIndicatorCSS) | renamed         | —                                                                                                                                                    |
| `timeout`                | [`defaultTimeout`](/reference/config/htmx-config-defaultTimeout)           | renamed         | —                                                                                                                                                    |
| `defaultTimeout`         | `60000`                                                                    | default changed | Was `0` (no timeout).                                                                                                                                |
| `defaultSettleDelay`     | `1`                                                                        | default changed | Was `20`.                                                                                                                                            |
| `addedClass`             | —                                                                          | removed         | The `htmx-added` CSS class is still applied during swaps.                                                                                            |
| `allowEval`              | —                                                                          | removed         | —                                                                                                                                                    |
| `allowNestedOobSwaps`    | —                                                                          | removed         | —                                                                                                                                                    |
| `allowScriptTags`        | —                                                                          | removed         | —                                                                                                                                                    |
| `attributesToSettle`     | —                                                                          | removed         | —                                                                                                                                                    |
| `defaultSwapDelay`       | —                                                                          | removed         | —                                                                                                                                                    |
| `disableSelector`        | [`hx-ignore`](/reference/attributes/hx-ignore)                             | removed         | —                                                                                                                                                    |
| `getCacheBusterParam`    | —                                                                          | removed         | —                                                                                                                                                    |
| `historyCacheSize`       | —                                                                          | removed         | —                                                                                                                                                    |
| `ignoreTitle`            | —                                                                          | removed         | Still works per swap via [`hx-swap`](/reference/attributes/hx-swap)`="... ignoreTitle:true"`.                                                     |
| `inlineStyleNonce`       | —                                                                          | removed         | Indicator CSS now uses [Constructable Stylesheets](https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleSheet/CSSStyleSheet), so no nonce is needed. |
| `methodsThatUseUrlParams` | —                                                                         | removed         | —                                                                                                                                                    |
| `refreshOnHistoryMiss`   | —                                                                          | removed         | —                                                                                                                                                    |
| `responseHandling`       | [`hx-status`](/reference/attributes/hx-status)                             | removed         | Use with [`noSwap`](/reference/config/htmx-config-noSwap).                                                                                          |
| `scrollBehavior`         | —                                                                          | removed         | —                                                                                                                                                    |
| `scrollIntoViewOnBoost`  | —                                                                          | removed         | —                                                                                                                                                    |
| `selfRequestsOnly`       | [`htmx.config.mode`](/reference/config/htmx-config-mode)                   | removed         | —                                                                                                                                                    |
| `settlingClass`          | —                                                                          | removed         | The `htmx-settling` CSS class is still applied during swaps.                                                                                         |
| `swappingClass`          | —                                                                          | removed         | The `htmx-swapping` CSS class is still applied during swaps.                                                                                         |
| `triggerSpecsCache`      | —                                                                          | removed         | —                                                                                                                                                    |
| `useTemplateFragments`   | —                                                                          | removed         | —                                                                                                                                                    |
| `withCredentials`        | [`hx-config`](/reference/attributes/hx-config)                             | removed         | —                                                                                                                                                    |
| `wsBinaryType`           | —                                                                          | removed         | —                                                                                                                                                    |
| `wsReconnectDelay`       | —                                                                          | removed         | —                                                                                                                                                    |

#### Request headers

| htmx 2.x          | htmx 4.x                                                | Type            | Notes                                                 |
|-------------------|---------------------------------------------------------|-----------------|-------------------------------------------------------|
| `HX-Trigger`      | [`HX-Source`](/reference/headers/HX-Source)             | changed format  | Format is `tagName#id`, e.g. `button#submit`.        |
| `HX-Target`       | [`HX-Target`](/reference/headers/HX-Target)             | changed format  | Format is `tagName#id`.                              |
| `HX-Trigger-Name` | [`HX-Source`](/reference/headers/HX-Source)             | removed         | —                                                     |
| `HX-Prompt`       | [`hx-prompt` extension](/extensions/hx-prompt)          | restored by ext | Load the extension.                                   |
| —                 | [`HX-Request-Type`](/reference/headers/HX-Request-Type) | new             | `"full"` or `"partial"`.                          |
| —                 | `Accept`                                                | new             | Core requests send `text/html`; [`hx-sse`](/extensions/hx-sse#request-headers) adds `text/event-stream`. |

#### Response headers

| htmx 2.x                 | htmx 4.x                                      | Type      | Notes                           |
|--------------------------|-----------------------------------------------|-----------|---------------------------------|
| `HX-Trigger-After-Swap`  | [`HX-Trigger`](/reference/headers/HX-Trigger) | removed   | You can also use JavaScript.    |
| `HX-Trigger-After-Settle`| [`HX-Trigger`](/reference/headers/HX-Trigger) | removed   | You can also use JavaScript.    |
| `HX-Trigger`             | [`HX-Trigger`](/reference/headers/HX-Trigger) | unchanged | —                               |
| `HX-Location`            | [`HX-Location`](/reference/headers/HX-Location) | unchanged | —                             |
| `HX-Push-Url`            | [`HX-Push-Url`](/reference/headers/HX-Push-Url) | unchanged | —                             |
| `HX-Redirect`            | [`HX-Redirect`](/reference/headers/HX-Redirect) | unchanged | —                             |
| `HX-Refresh`             | [`HX-Refresh`](/reference/headers/HX-Refresh) | unchanged | —                               |
| `HX-Replace-Url`         | [`HX-Replace-Url`](/reference/headers/HX-Replace-Url) | unchanged | —                       |
| `HX-Retarget`            | [`HX-Retarget`](/reference/headers/HX-Retarget) | unchanged | —                             |
| `HX-Reswap`              | [`HX-Reswap`](/reference/headers/HX-Reswap)   | unchanged | —                               |
| `HX-Reselect`            | [`HX-Reselect`](/reference/headers/HX-Reselect) | unchanged | —                             |

#### JavaScript API

| htmx 2.x                 | htmx 4.x                      | Type      | Notes                                                                    |
|--------------------------|-------------------------------|-----------|--------------------------------------------------------------------------|
| `htmx.addClass()`        | `element.classList.add()`     | removed   | Use native JavaScript.                                                   |
| `htmx.removeClass()`     | `element.classList.remove()`  | removed   | Use native JavaScript.                                                   |
| `htmx.toggleClass()`     | `element.classList.toggle()`  | removed   | Use native JavaScript.                                                   |
| `htmx.closest()`         | `element.closest()`           | removed   | Use native JavaScript.                                                   |
| `htmx.remove()`          | `element.remove()`            | removed   | Use native JavaScript.                                                   |
| `htmx.off()`             | `removeEventListener()`       | removed   | `htmx.on()` returns the callback.                                        |
| `htmx.location()`        | `htmx.ajax()`                 | removed   | —                                                                        |
| `htmx.defineExtension()` | `htmx.registerExtension()`    | renamed   | —                                                                        |
| `htmx.logAll()`          | `htmx.config.logAll = true`   | removed   | htmx logs via `console.*`.                                               |
| `htmx.logNone()`         | —                             | removed   | —                                                                        |
| `htmx.logger`            | `console.*`                   | removed   | Observability tools capture `console.error`, `console.warn`, and `console.log`. |
| `htmx.onLoad()`          | `htmx.onLoad()`               | changed   | Now listens on [`htmx:after:process`](/reference/events/htmx-after-process), not [`htmx:after:init`](/reference/events/htmx-after-init). |

Still available: `htmx.ajax()`, `htmx.config`, `htmx.find()`, `htmx.findAll()`, `htmx.on()`, `htmx.onLoad()`, `htmx.parseInterval()`, `htmx.process()`, `htmx.swap()`, `htmx.trigger()`.

### What's New

#### Attributes

| Attribute                                          | Purpose                                                               |
|----------------------------------------------------|-----------------------------------------------------------------------|
| [`hx-action`](/reference/attributes/hx-action)     | Specify URL, with optional [`hx-method`](/reference/attributes/hx-method). Supports progressive enhancement via native `action`/`method` fallback |
| [`hx-method`](/reference/attributes/hx-method)     | Specify HTTP method (overrides native `method` and `formmethod`)      |
| [`hx-config`](/reference/attributes/hx-config)     | Per-element request config (JSON or `key:value` syntax)               |
| [`hx-ignore`](/reference/attributes/hx-ignore)     | Disable htmx processing (was `hx-disable`)                            |
| [`hx-validate`](/reference/attributes/hx-validate) | Control form validation behavior                                      |

#### [`hx-swap`](/reference/attributes/hx-swap) scroll modifiers

The `show` and `scroll` modifiers no longer support the combined `selector:position` syntax. Use separate keys instead:

```html
<!-- htmx 2 (broken in 4) -->
<div hx-swap="innerHTML show:#other:top"></div>

<!-- htmx 4 -->
<div hx-swap="innerHTML show:top showTarget:#other"></div>
<div hx-swap="innerHTML scroll:bottom scrollTarget:#other"></div>
```

#### [`hx-swap`](/reference/attributes/hx-swap) styles

```html

<div hx-get="/data" hx-swap="innerMorph">...</div>
<div hx-get="/data" hx-swap="outerMorph">...</div>
<div hx-get="/text" hx-swap="textContent">...</div>
<div hx-get="/remove" hx-swap="delete">...</div>
```

- `innerMorph` / `outerMorph`: morph swaps using the idiomorph algorithm. Better for preserving state in complex UIs.
- `textContent`: set the target's text content (no HTML parsing).
- `delete`: remove the target element entirely.

New aliases for existing swap styles (both old and new names work):

| New       | Equivalent to |
|-----------|---------------|
| `before`  | `beforebegin` |
| `after`   | `afterend`    |
| `prepend` | `afterbegin`  |
| `append`  | `beforeend`   |

#### [Status code swaps](/reference/attributes/hx-status)

Set different swap behavior per HTTP status code:

```html

<form hx-post="/save"
      hx-status:422="swap:innerHTML target:#errors select:#validation-errors"
      hx-status:5xx="swap:none push:false">
    <!-- form fields -->
</form>
```

Available config keys: `swap:`, `target:`, `select:`, `push:`, `replace:`, `transition:`.

Supports exact codes (`404`), single-digit wildcards (`50x`), and range wildcards (`5xx`). Evaluated in order of
specificity.

#### `<hx-partial>`

Target multiple elements from one response. An alternative to [`hx-swap-oob`](/reference/attributes/hx-swap-oob) for when you need explicit control over targeting and swap strategy:

```html
<hx-partial hx-target="#messages" hx-swap="beforeend">
    <div>New message</div>
</hx-partial>

<hx-partial hx-target="#count">
    <span>5</span>
</hx-partial>
```

Each `<hx-partial>` specifies its own [`hx-target`](/reference/attributes/hx-target) and [`hx-swap`](/reference/attributes/hx-swap) strategy. See [Multi-Target Updates](#multi-target-updates) for full documentation.

#### View transitions

[View Transitions API](https://developer.mozilla.org/en-US/docs/Web/API/View_Transition_API) support is available but
disabled by default.

Enable: [`htmx.config.transitions`](/reference/config/htmx-config-transitions) `= true`

#### JSX compatibility

Frameworks that don't support `:` in attribute names can use [
`metaCharacter`](/reference/config/htmx-config-metaCharacter) to replace it:

```js
htmx.config.metaCharacter = "-";
// hx-ws-connect instead of hx-ws:connect
// hx-confirm-inherited instead of hx-confirm:inherited
```

#### JavaScript methods

- `htmx.timeout(time)`: returns a promise that resolves after a delay (number ms, or interval string `'500ms'`/`'1s'`/`'5m'`)

`htmx.takeClass` is **removed** from core. Equivalent functionality is exposed by the `hx-live` extension on the `htmx.live` namespace:

```js
htmx.live.take(target, className, source)   // strip class from `source`, add to `target`
htmx.live.forEvent(...args)                 // race events/timeouts
htmx.live.nextFrame()                       // promise that resolves on next animation frame
htmx.live.q(selector)                       // jQuery-like proxy rooted at documentElement
htmx.live.debounce(ms[, fn])                // global debounce
htmx.live.refresh()                         // recompute every live expression
```

Inside `hx-live`/`hx-on` expression scope these are available unprefixed (`take`, `forEvent`, `nextFrame`, `q`, `debounce`, `toggle`) with the current element used as the implicit context, see the [`hx-live` extension docs](/extensions/hx-live).

#### Auto-logged events

Internally-dispatched events route to the console as follows:

- If `detail.error` is set on the event, output goes to `console.error` (the Error instance is inlined first when applicable, so DevTools renders the stack). This covers request failures, hx-on handler exceptions, and other thrown paths. Apps that listen for `htmx:error` get the same data via the event.
- If `detail.warn` is set, output goes to `console.warn`.
- Otherwise, the event is logged at `console.log` (silent by default; set `htmx.config.logAll = true` to surface).

This restores the htmx 2.x convention: if you want an internal failure path to show up in the console, fire an event with `detail.error` (or `detail.warn`); no per-site `console.error` needed.

#### Request context

All events provide a consistent `ctx` object with request/response information.

#### Events

| Event                                                                        | Fires                                             |
|------------------------------------------------------------------------------|---------------------------------------------------|
| [`htmx:after:cleanup`](/reference/events/htmx-after-cleanup)                 | After element cleanup                             |
| [`htmx:after:history:update`](/reference/events/htmx-after-history-update)   | After history update                              |
| [`htmx:after:process`](/reference/events/htmx-after-process)                 | After element processing                          |
| [`htmx:before:response`](/reference/events/htmx-before-response)             | Before response body is read (cancellable)        |
| [`htmx:before:settle`](/reference/events/htmx-before-settle)                 | Before settle phase                               |
| [`htmx:after:settle`](/reference/events/htmx-after-settle)                   | After settle phase                                |
| [`htmx:before:viewTransition`](/reference/events/htmx-before-viewTransition) | Before a view transition starts (cancellable)     |
| [`htmx:after:viewTransition`](/reference/events/htmx-after-viewTransition)   | After a view transition completes                 |
| [`htmx:finally:request`](/reference/events/htmx-finally-request)             | When request completes, fails, or is cancelled |

#### Config keys

| Config                                                                 | Default         | Purpose                                                       |
|------------------------------------------------------------------------|-----------------|---------------------------------------------------------------|
| [`extensions`](/reference/config/htmx-config-extensions)               | `''`            | Comma-separated list of allowed extension names               |
| [`mode`](/reference/config/htmx-config-mode)                           | `'same-origin'` | Fetch mode (replaces `selfRequestsOnly`)                      |
| [`inlineScriptNonce`](/reference/config/htmx-config-inlineScriptNonce) | `''`            | Nonce for inline scripts                                      |
| [`metaCharacter`](/reference/config/htmx-config-metaCharacter)         | `':'`           | Separator character in attribute/event names                  |
| [`morphIgnore`](/reference/config/htmx-config-morphIgnore)             | `["data-htmx-powered"]` | Attribute name prefixes to preserve during morph              |
| [`morphScanLimit`](/reference/config/htmx-config-morphScanLimit)       |                 | Max elements to scan during morph matching                    |
| [`morphSkip`](/reference/config/htmx-config-morphSkip)                 | `'[hx-morph-skip]'`            | CSS selector for elements to skip during morph                |
| [`morphSkipChildren`](/reference/config/htmx-config-morphSkipChildren) | `'[hx-morph-skip-children]'`            | CSS selector for elements whose children to skip during morph |

#### Core extensions

htmx 4 ships with 9 core extensions. The SSE and WebSocket extensions have been significantly rewritten. See their upgrade guides for details.

| Extension                                                 | Description                                                                          |
|-----------------------------------------------------------|--------------------------------------------------------------------------------------|
| [`alpine-compat`](/extensions/hx-alpine-compat)         | Alpine.js compatibility: initializes Alpine on fragments before swap                 |
| [`browser-indicator`](/extensions/hx-browser-indicator) | Shows the browser's native loading indicator during requests                         |
| [`head-support`](/extensions/hx-head)           | Merges head tag information (styles, etc.) in htmx requests                          |
| [`htmx-2-compat`](/extensions/htmx-2-compat)         | Restores implicit inheritance, old event names, and previous error-swapping defaults |
| [`optimistic`](/extensions/hx-optimistic)               | Shows expected content from a template before the server responds                    |
| [`preload`](/extensions/hx-preload)                     | Triggers requests early (on mouseover/mousedown) for near-instant page loads ([upgrade guide](/extensions/hx-preload#upgrading-from-htmx-2x)) |
| [`sse`](/extensions/hx-sse)                             | Server-Sent Events streaming support ([upgrade guide](/extensions/hx-sse#upgrading-from-htmx-2x)) |
| [`upsert`](/extensions/hx-upsert)                       | Updates existing elements by ID and inserts new ones, preserving unmatched elements  |
| [`ws`](/extensions/hx-ws)                               | Bi-directional WebSocket communication ([upgrade guide](/extensions/hx-ws#upgrading-from-htmx-2x)) |

### Checklist

1. Optionally, add config options or load [`htmx-2-compat`](/extensions/htmx-2-compat) for backward compatibility
2. Run the [upgrade checker](#upgrade-checker) to get a full list of issues
3. Rename `hx-disable` to [`hx-ignore`](/reference/attributes/hx-ignore), then `hx-disabled-elt` to [
   `hx-disable`](/reference/attributes/hx-disable)
4. Replace removed attributes with alternatives
5. Find/replace event names in JavaScript and `hx-on` attributes
6. Replace removed API methods with native JS
7. Update extensions
8. Rename changed config keys
9. Test error handling (4xx/5xx now swap by default)
10. Test attribute inheritance
11. Test history navigation

### Migration Notes

Individual documentation pages include migration notes where features changed.

Look for these:

<details class="warning">
<summary>Changes in htmx 4.0</summary>

</details>

### Get Help

- [GitHub Discussions](https://github.com/bigskysoftware/htmx/discussions)
- [Discord](https://htmx.org/discord)
- [Patterns](/patterns)


### Migrating Your Own Extensions

{/* Audience shifts here from htmx consumers to extension authors. */}

If you maintain a custom extension written for htmx 2.x, the extension API has changed substantially. The catalog of bundled extensions ([/extensions](/extensions)) has already been ported. This section is for porting your own.

#### From Callbacks to Hooks

htmx 4 replaces the callback-based extension API with event-based hooks. Extensions register handlers for lifecycle events instead of implementing callback methods.

The simplest migration: rename `defineExtension` to `registerExtension` and map your callbacks to hooks.

```javascript
// htmx 2.x
htmx.defineExtension('my-ext', {
    onEvent: function(name, evt) {
        if (name === 'htmx:beforeRequest') { /* ... */ }
    }
});

// htmx 4
htmx.registerExtension('my-ext', {
    htmx_before_request: (elt, detail) => { /* ... */ }
});
```

#### What Changed

##### No `hx-ext` attribute

Extensions load by including the script. No attribute needed:

```html
<script src="/path/to/htmx.js"></script>
<script src="/path/to/ext/my-extension.js"></script>
```

Restrict which extensions can load:

```html
<meta name="htmx-config" content='{"extensions": "sse, ws"}'>
```

##### Event hooks replace callbacks

Instead of a single `onEvent` callback that switches on event names, each event gets its own hook method. Hook names use underscores where events use colons:

| htmx 2.x event | htmx 4 hook |
|---|---|
| `htmx:configRequest` | `htmx_config_request` |
| `htmx:beforeRequest` | `htmx_before_request` |
| `htmx:afterRequest` | `htmx_after_request` |
| `htmx:beforeSwap` | `htmx_before_swap` |
| `htmx:afterSwap` | `htmx_after_swap` |

All hooks receive `(elt, detail)`. Return `false` to cancel.

##### `handle_swap` is special

Unlike other hooks, `handle_swap` is called directly with positional parameters (no `htmx_` prefix, no detail object):

```javascript
handle_swap: (swapStyle, target, fragment, swapSpec) => {
    if (swapStyle === 'my-swap') {
        target.appendChild(fragment);
        return true;
    }
    return false;
}
```

##### Detail object replaces event properties

All hooks receive `detail.ctx` with full request/response context:

- `detail.ctx.request.body` (FormData in `htmx_config_request`)
- `detail.ctx.request.headers` (plain mutable object)
- `detail.ctx.response.status`
- `detail.ctx.text` (response body, modifiable in `htmx_after_request`)
- `detail.ctx.target`

##### OOB swap stripping

OOB swaps automatically strip the wrapper element for non-outer swap styles. Name custom swap styles starting with "outer" (e.g., `outerMorph`) to preserve the wrapper.

#### Callback Migration Map

##### `init`

```javascript
// htmx 2.x
init: function(api) {
    return null;
}

// htmx 4
init: (internalAPI) => {
    api = internalAPI;
}
```

Store the `internalAPI` reference for use in other hooks. No return value needed.

##### `getSelectors`

Removed. Use `htmx_after_init` to check for attributes:

```javascript
// htmx 2.x
getSelectors: function() {
    return ['[my-custom-attr]'];
},
onEvent: function(name, evt) {
    if (name === 'htmx:afterProcessNode') {
        initializeCustomBehavior(evt.target);
    }
}

// htmx 4
htmx_after_init: (elt) => {
    if (api.attributeValue(elt, 'my-custom-attr')) {
        initializeCustomBehavior(elt);
    }
}
```

##### `onEvent`

Replace with individual hooks:

```javascript
// htmx 2.x
onEvent: function(name, evt) {
    if (name === 'htmx:beforeSwap' && evt.detail.xhr.status !== 200) {
        var target = getRespCodeTarget(evt.detail.requestConfig.elt, evt.detail.xhr.status);
        if (target) {
            evt.detail.shouldSwap = true;
            evt.detail.target = target;
        }
    }
}

// htmx 4
htmx_before_swap: (elt, detail) => {
    if (detail.ctx.response.status !== 200) {
        var target = getRespCodeTarget(elt, detail.ctx.response.status);
        if (target) {
            detail.ctx.target = target;
        }
    }
}
```

##### `transformResponse`

Removed. Modify `detail.ctx.text` in `htmx_after_request`:

```javascript
// htmx 2.x
transformResponse: function(text, xhr, elt) {
    var tpl = htmx.closest(elt, '[mustache-template]');
    if (tpl) {
        var data = JSON.parse(text);
        var template = htmx.find('#' + tpl.getAttribute('mustache-template'));
        return Mustache.render(template.innerHTML, data);
    }
    return text;
}

// htmx 4
htmx_after_request: (elt, detail) => {
    var tpl = elt.closest('[mustache-template]');
    if (tpl) {
        var data = JSON.parse(detail.ctx.text);
        var template = document.querySelector('#' + tpl.getAttribute('mustache-template'));
        detail.ctx.text = Mustache.render(template.innerHTML, data);
    }
}
```

Event flow: response received, `ctx.text` set, `htmx:after:request` fires, `ctx.text` consumed into fragment, `htmx:before:swap`.

##### `encodeParameters`

Removed. Modify the final `detail.ctx.request.body` in `htmx_before_request`:

```javascript
// htmx 2.x
onEvent: function(name, evt) {
    if (name === 'htmx:configRequest') {
        evt.detail.headers['Content-Type'] = 'application/json';
    }
},
encodeParameters: function(xhr, parameters, elt) {
    var object = {};
    parameters.forEach(function(value, key) {
        if (Object.hasOwn(object, key)) {
            if (!Array.isArray(object[key])) object[key] = [object[key]];
            object[key].push(value);
        } else {
            object[key] = value;
        }
    });
    return JSON.stringify(object);
}

// htmx 4
htmx_before_request: (elt, detail) => {
    detail.ctx.request.headers['Content-Type'] = 'application/json';
    var object = {};
    detail.ctx.request.body.forEach(function(value, key) {
        if (Object.hasOwn(object, key)) {
            if (!Array.isArray(object[key])) object[key] = [object[key]];
            object[key].push(value);
        } else {
            object[key] = value;
        }
    });
    detail.ctx.request.body = JSON.stringify(object);
}
```

`ctx.request.body` is `FormData` in `htmx_config_request`. After that hook, GET and DELETE values move into the URL, non-multipart bodies become `URLSearchParams`, and multipart bodies remain `FormData`. In `htmx_before_request`, replace the final body with any valid `BodyInit`.

##### `isInlineSwap` and `handleSwap`

Both replaced by `handle_swap`:

```javascript
// htmx 2.x
isInlineSwap: function(swapStyle) {
    return swapStyle === 'morphdom';
},
handleSwap: function(swapStyle, target, fragment) {
    if (swapStyle === 'morphdom') {
        morphdom(target, fragment.firstElementChild || fragment.firstChild);
        return [target];
    }
}

// htmx 4
handle_swap: (swapStyle, target, fragment) => {
    if (swapStyle === 'morphdom') {
        morphdom(target, fragment.firstElementChild || fragment.firstChild);
        return true;
    }
    return false;
}
```

Return truthy if handled, falsy otherwise. Can return an array of elements for settle tracking.

#### Removed Callbacks

| htmx 2.x callback                       | htmx 4 replacement                                            |
|-----------------------------------------|---------------------------------------------------------------|
| `getSelectors()`                        | `htmx_after_init` hook                                        |
| `onEvent(name, evt)`                    | Individual `htmx_*` hooks                                     |
| `transformResponse(text, xhr, elt)`     | `htmx_after_request` hook (modify `detail.ctx.text`)          |
| `encodeParameters(xhr, params, elt)`    | `htmx_before_request` hook (modify final `detail.ctx.request.body`) |
| `isInlineSwap(swapStyle)`               | `handle_swap` or name swap style with "outer" prefix          |
| `handleSwap(style, target, frag, info)` | `handle_swap(style, target, frag, spec)`                      |

#### Checklist

1. Rename `defineExtension` to `registerExtension`
2. Replace `onEvent` with individual `htmx_*` hooks
3. Replace `transformResponse` with `htmx_after_request`
4. Replace `encodeParameters` with `htmx_before_request`
5. Merge `isInlineSwap` and `handleSwap` into `handle_swap`
6. Replace `getSelectors` with `htmx_after_init`
7. Remove `hx-ext` attributes from HTML
8. Update event names (colons to underscores in hook names)
9. Test custom swap styles with OOB swaps

<hr data-sidebar-group="Core Concepts" />

## Mental Model

{/* TODO: Add section for developers coming from React/Vue/Svelte -
     cover the mindset shift from "client builds UI from JSON" to
     "server sends ready-to-render HTML". Address common misconceptions
     and patterns to unlearn. */}

htmx extends HTML's built-in concept of [hypermedia controls](https://dl.acm.org/doi/fullHtml/10.1145/3648188.3675127).

To understand htmx, you should first understand what these are.

### HTML's Native Controls

HTML has two major elements that issue HTTP requests in response to user actions: `<a>` (anchors, aka "links") and
`<form>`.

#### The Anchor Tag

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

When a user click this link a browser will issue an HTTP `GET` request to `/blog`. It will then load the HTML response
into the browser's window.

#### The Form Tag

```html

<form method="post" action="/register">
    <label>Email: <input type="email"></label>
    <button type="submit">Submit</button>
</form>
```

When a user submits this form (by, say, clicking on the "Submit" button) a browser will issue an HTTP `POST` request to
`/register`. Again, it will load the HTML response to this request into the browser window.

These two hypermedia controls demonstrate the core idea behind them: in response to a user action a
request is made and new content is loaded into the client.

### Transclusion

Both of these HTML elements support a (relatively little-known and unused)
[`target`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLAnchorElement/target) attribute.

By using this attribute you can place the response in, for example, an iframe rather than replacing the entire
content in the window:

```html

<form method="post" action="/register" target="iframe1">
    <label>Email: <input type="email"></label>
    <button type="submit">Submit</button>
</form>
<iframe name="iframe1">
    <!-- Response appears here -->
</iframe>
```

This is [transclusion](https://en.wikipedia.org/wiki/Transclusion), where one HTML document is included inside of
another.

### How htmx Extends This Idea

htmx generalizes these concepts. Any element can issue any type of HTTP request to any URL. Any event can trigger the
request. And the response HTML can be placed anywhere in the DOM.

```html

<button hx-post="/clicked"
        hx-trigger="click"
        hx-target="#output"
        hx-swap="outerHTML">
    Click Me
</button>
<output id="output"></output>
```

These htmx attributes (which start with `hx-`) tell a browser:

> When a user clicks this button, issue a POST request to `/clicked`. Use the response to replace the element with id
`output`.

Like anchor and form tags, htmx expects _HTML responses_ from the server.

This is in contrast with many front-end libraries and frameworks today which instead expect JSON and
use client-side templating to transform that JSON into HTML on the client.

### htmx's Philosophy

Because htmx works in terms of HTML it follows the [original web programming model](https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm). 
It uses [Hypertext As The Engine Of Application State](https://en.wikipedia.org/wiki/HATEOAS) (HATEOAS).

The server controls what the user sees by sending HTML.  Users select actions from that HTML and the server responds with
more HTML (i.e. hypertext). 

Thus, the hypermedia itself drives the application.

## Hypermedia Controls

htmx extends HTML with attributes that control how requests are made and how responses update the page.

### Making Requests

Add [`hx-get`](/reference/attributes/hx-get) to an element. It makes an AJAX request when clicked.

**Your HTML:**
```html
<button hx-get="/messages">
    Load Messages
</button>
```

**What the server returns:**
```html
<div>You have 3 new messages</div>
```

**What the user sees:**
```html
<button hx-get="/messages">
    <div>You have 3 new messages</div>
</button>
```

The response replaced the button's content. No JavaScript required.

#### How It Works

| Step                  | What Happens                     |
|-----------------------|----------------------------------|
| 1. User clicks button | htmx intercepts the click        |
| 2. htmx makes request | Sends GET request to `/messages` |
| 3. Server responds    | Returns HTML (not JSON)          |
| 4. htmx updates page  | Swaps HTML into the button       |

#### HTTP Methods

Use different attributes for different operations:

```html
<button hx-get="/users">Load Users</button>
<button hx-post="/users">Create User</button>
<button hx-put="/users/1">Update User</button>
<button hx-patch="/users/1">Patch User</button>
<button hx-delete="/users/1">Delete User</button>
```

Each attribute combines the URL and HTTP method. Alternatively, use [`hx-action`](/reference/attributes/hx-action) and [`hx-method`](/reference/attributes/hx-method) to separate them — useful when the method is dynamic, or when you want progressive enhancement with native `action`/`method` fallback.

#### Common Patterns

**Load data on click:**
```html
<button hx-get="/profile">View Profile</button>
```

**Submit a form:**
```html
<form hx-post="/contact">
    <input name="email" type="email">
    <button type="submit">Send</button>
</form>
```

Form submits via AJAX instead of full page reload.

**Delete an item:**
```html
<button hx-delete="/items/5">Delete Item</button>
```

#### What Gets Sent

htmx sends standard HTTP requests:

**Request to server:**
```
GET /messages HTTP/1.1
HX-Request: true
HX-Target: button
```

htmx adds custom headers so your server knows it's an htmx request.

**Server response:**
```html
HTTP/1.1 200 OK
Content-Type: text/html

<div>You have 3 new messages</div>
```

Just HTML. No JSON parsing needed.

### Triggers

By default, 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`](/reference/attributes/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:

```html
<div hx-post="/mouse_entered" hx-trigger="mouseenter">
    Mouse Trap
</div>
```

#### Trigger Modifiers

A trigger can also have 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:

```html
<div hx-post="/mouse_entered" hx-trigger="mouseenter once">
    Mouse Trap
</div>
```

Other modifiers you can use for triggers are (parsed as [HCON](#hcon)):

* `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. Note that this CSS selector is not re-evaluated if the page changes.

Multiple triggers can be specified in the [`hx-trigger`](/reference/attributes/hx-trigger) attribute, separated by commas.

You can use these features to implement many common UX patterns, such as [Active Search](/patterns/active-search):

```html
<input type="text"
       name="q"
       placeholder="Search..."
       hx-get="/search"
       hx-trigger="input delay:500ms, keyup[key=='Enter']"
       hx-target="#search-results">
<div id="search-results"></div>
```

This input will issue a request 500 milliseconds after an input event occurs, or the `enter` key is pressed and inserts
the results into the `div` with the id `search-results`.

#### Trigger Filters

In the example above, you may have noticed the square brackets after the event name. This is called a "trigger filter".

Trigger filters allow you to place a filtering javascript expression after the event name that will prevent the trigger
if the filter does not return true.

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

```html
<div hx-get="/shift_clicked" hx-trigger="click[shiftKey]">
    Shift Click Me
</div>
```

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

The `this` symbol will be set to the current element.

#### Special Events

htmx provides a few special events for use in [`hx-trigger`](/reference/attributes/hx-trigger):

* `load` - fires once when the element is first loaded
* `revealed` - fires once when an element first scrolls into the viewport
* `intersect` - fires once when an element first intersects the viewport. This supports two additional options:
    * `root:<selector>` - a CSS selector of the root element for intersection
    * `threshold:<float>` - a floating point number between 0.0 and 1.0, indicating what amount of intersection to fire
      the event on

You can also use custom events to trigger requests.

#### Polling

Polling is a simple technique where a web page periodically issues a request to the server to see if any updates have
occurred. It is not very highly respected in many web development circles, but it is simple, can be relatively
resource-light because it does not maintain a constant network connection, and it tolerates network failures well

In htmx you can implement polling via the `every` syntax in the [`hx-trigger`](/reference/attributes/hx-trigger) attribute:

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

This tells htmx:

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

#### Load Polling

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

```html
<div hx-get="/messages"
     hx-trigger="load delay:1s"
     hx-swap="outerHTML">
</div>
```

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](/patterns/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.

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

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.

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

```css
.htmx-indicator {
    display: none;
}

.htmx-request .htmx-indicator {
    display: inline;
}

.htmx-request.htmx-indicator {
    display: inline;
}
```

If you want the `htmx-request` class added to a different element, you can use
the [`hx-indicator`](/reference/attributes/hx-indicator)
attribute with a CSS selector to do so:

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

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.

You can also add the [`disabled` attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/disabled) to
elements for the duration of a request by using the [`hx-disable`](/reference/attributes/hx-disable) attribute.

### Targets

By default, responses replace the element that made the request.

Change this with [`hx-target`](/reference/attributes/hx-target).

```html
<button hx-get="..."
        hx-target="#results">
  Load Results
</button>

<div id="results">
    <!-- Response goes here -->
</div>
```

The button makes the request.

The response loads into `#results`.

The button stays unchanged.

#### Extended Selectors
Use [extended selectors](#extended-selectors-1) to target elements flexibly.

Beyond standard CSS selectors, you can use:

* [`this`](#this) - target the element itself
* [`closest <selector>`](#closest-selector) - find the nearest ancestor
* [`find <selector>`](#find-selector) - find the first child
* [`next`](#next) - target the next sibling
* [`next <selector>`](#next-selector) - find next sibling matching `<selector>`
* [`previous`](#previous) - target the previous sibling
* [`previous <selector>`](#previous-selector) - find previous sibling matching `<selector>`
* And more...

See the full [extended selectors guide](#extended-selectors-1) for all options and examples.

This keeps your HTML cleaner without requiring `id` attributes everywhere.

### Swaps

htmx offers many different ways to swap the HTML returned into the DOM. By default, the content replaces the
[innerHTML](https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML) of the target element, which is called
an `innerHTML` swap.

This is similar to how the `target` attribute on links and forms works, placing the retrieved document within an iframe.

You can modify this by using the [`hx-swap`](/reference/attributes/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                                                                              |
| `beforebegin` (or `before`) | prepends the content before the target in the target's parent element                                                                     |
| `afterbegin` (or `prepend`) | prepends the content before the first child inside the target                                                                             |
| `beforeend` (or `append`)   | appends the content after the last child inside the target                                                                                |
| `afterend` (or `after`)     | appends the content after the target in the target's parent element                                                                       |
| `delete`                    | deletes the target element regardless of the response                                                                                     |
| `none`                      | does not append content from response ([Out of Band Swaps](#oob_swaps) and [Response Headers](#response-headers) will still be processed) |
| `innerMorph`                | morphs the children of the target element, preserving as much of the existing DOM as possible                                             |
| `outerMorph`                | morphs the target element itself, preserving as much of the existing DOM as possible                                                      |
| `textContent`               | Set the target's text content (no HTML parsing)                                                                                           |

#### Morph Swaps

htmx includes built-in `innerMorph` and `outerMorph` swaps that merge new content into the existing DOM rather than
simply replacing it. They often do a better job preserving things like focus, video state, etc. by mutating existing
nodes in-place during the swap operation, at the cost of more CPU.

Consider this HTML:

```html
<div id="video-elt">
    <h1>Title</h1>
    <iframe id="video" width="791" height="445" src="https://www.youtube.com/embed/dQw4w9WgXcQ"></iframe>
</div>
<button hx-get="/swap"
        hx-target="#video-elt"
        hx-swap="outerMorph">
    Swap Header To Bottom
</button>
```

If the response content for this looks like this:

```html
<div id="video-elt">
    <iframe id="video" width="791" height="445" src="https://www.youtube.com/embed/dQw4w9WgXcQ"></iframe>
    <h1>Title</h1>
</div>
```

Then htmx will "morph" the existing content to the new structure. Note that the `h1` element has moved below the
video. With the `outerHTML` swap this will cause the video to stop playing and reset. However, the morphing algorithm
uses ID elements to intelligently mutate the DOM and preserve the existing video element, keeping the video playing
smoothly.

Note that a similar effect can be achieved with the [`hx-preserve`](/reference/attributes/hx-preserve) attribute, discussed below.

##### Excluding Elements from Morphing

Add `hx-morph-skip` or `hx-morph-skip-children` to elements in your server templates:

- `hx-morph-skip` - completely skip morphing specific elements (attrs + children stay frozen)
- `hx-morph-skip-children` - update element attributes but preserve children

Or configure globally with CSS selectors:

```javascript
htmx.config.morphSkip         = 'custom-widget, .frozen';
htmx.config.morphSkipChildren = 'lit-component, .sortable';
```

Useful for third-party widgets, custom web components, or active animations.

#### View Transitions

The [View Transitions API](https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API)
gives developers a way to create an animated transition between different DOM states.

htmx supports view transitions via:
- Setting `htmx.config.transitions` to `true` globally
- Per-swap via `hx-swap` `transition` property: `hx-swap="outerHTML transition:true"`
- For boosted elements: [`hx-boost`](/reference/attributes/hx-boost)`="transition:true"`

#### Swap Options

The [`hx-swap`](/reference/attributes/hx-swap) attribute also supports options for tuning the swapping behavior of htmx. For
example, by default htmx will swap in the title of a title tag found anywhere in the new content. You can turn this
behavior off by setting the `ignoreTitle` modifier to true:

```html
<button hx-post="/like" hx-swap="outerHTML ignoreTitle:true">Like</button>
```

The modifiers available on `hx-swap` are (parsed as [HCON](#hcon)):

| Option       | Description                                                                                          |
|--------------|------------------------------------------------------------------------------------------------------|
| swap         | A time interval (e.g., 100ms, 1s) to delay the swap operation                                        |
| transition   | true or false, whether to use the view transition API for this swap                                  |
| ignoreTitle  | If set to true, any title found in the new content will be ignored and not update the document title |
| strip        | true or false, whether to strip the outer element when swapping (unwrap the content)                 |
| focusScroll  | true or false, whether to scroll focused elements into view                                          |
| swapEmpty    | true or false, whether to perform the main swap when the response body is empty (`false` skips it). Defaults to [`htmx.config.defaultSwapEmpty`](/reference/config/htmx-config-defaultSwapEmpty) |
| scroll       | top or bottom, will scroll the target element to its top or bottom                                   |
| show         | top or bottom, will scroll the target element's top or bottom into view                              |
| target       | A selector to retarget the swap to a different element                                               |

All swap modifiers appear after the swap style is specified, and are colon-separated.

See the [`hx-swap`](/reference/attributes/hx-swap) documentation for more details on these options.

### Parameters

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.

As with HTML forms, the `name` attribute of the input is used as the parameter name in the request that htmx sends.

Additionally, if the element causes a non-`GET` request, the values of all the inputs of the associated form will be
included (typically this is the nearest enclosing form, but could be different if e.g. `<button form="associated-form">`
is used).

If you wish to include the values of other elements, you can use the [`hx-include`](/reference/attributes/hx-include) attribute
with a CSS selector of all the elements whose values you want to include in the request.

Finally, if you want to programmatically modify the parameters, you can use the [`htmx:config:request`](/reference/events/htmx-config-request)
event.

#### File Upload

If you wish to upload files via an htmx request, you can set the [`hx-encoding`](/reference/attributes/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.

## 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`](/reference/attributes/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](#response-headers)).

For this situation, by default, you can return a `204 - No Content` response code, and htmx will ignore the content of
the response.

In the event of a connection error, the [`htmx:error`](/reference/events/htmx-error) event will be triggered.

#### Configuring Response Handling 

By default, htmx will swap content for all HTTP responses except `204` and `304` status codes. This includes error
responses (4xx, 5xx). You can customize this behavior using the [`hx-status`](/reference/attributes/hx-status) attribute pattern (`hx-status:XXX`) or by configuring
`htmx.config.noSwap`.

##### Status-Code Conditional Swapping

The `hx-status:XXX` attribute allows you to specify different swap behaviors based on the HTTP status code of the
response.
This gives you fine-grained control over how different response statuses are handled.

```html
<button hx-get="/data"
        hx-status:404="none"
        hx-status:500="target:#error-container">
    Load Data
</button>
```

```html
<form hx-post="/submit"
      hx-target="#result"
      hx-status:422="target:#validation-errors"
      hx-status:500="target:#server-error"
      hx-status:503="none">
    <input name="email">
    <button type="submit">Submit</button>
</form>

<div id="result"></div>
<div id="validation-errors"></div>
<div id="server-error"></div>
```

In this example:

- Successful responses (2xx) swap into `#result` (default behavior)
- `422` responses swap into `#validation-errors`
- `500` responses swap into `#server-error`
- `503` responses don't swap at all

#### Request Headers

htmx includes headers in the requests it makes:

| Header                       | Description                                                                                          |
|------------------------------|------------------------------------------------------------------------------------------------------|
| [`HX-Boosted`](/reference/headers/HX-Boosted)                 | indicates that the request is via an element using [`hx-boost`](/reference/attributes/hx-boost)                  |
| [`HX-Current-URL`](/reference/headers/HX-Current-URL)             | the current URL of the browser                                                                       |
| [`HX-Request`](/reference/headers/HX-Request)                 | always "true"                                                                                        |
| [`HX-Request-Type`](/reference/headers/HX-Request-Type)            | `"partial"` for targeted swaps, `"full"` for body-level or `hx-select` requests                      |
| [`HX-Source`](/reference/headers/HX-Source)                  | the source element in `tag#id` format (e.g. `button#submit`)                                         |
| [`HX-Target`](/reference/headers/HX-Target)                  | the target element in `tag#id` format (e.g. `div#results`)                                           |

#### Response Headers

htmx supports htmx-specific response headers:

| Header                                           | Description                                                                                                                                                                        |
|--------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| [`HX-Location`](/reference/headers/HX-Location)            | allows you to do a client-side redirect that does not do a full page reload                                                                                                        |
| [`HX-Push-Url`](/reference/headers/HX-Push-Url)            | pushes a new url into the history stack                                                                                                                                            |
| [`HX-Redirect`](/reference/headers/HX-Redirect)            | can be used to do a client-side redirect to a new location                                                                                                                         |
| [`HX-Refresh`](/reference/headers/HX-Refresh)                                     | if set to "true" the client-side will do a full refresh of the page                                                                                                                |
| [`HX-Replace-Url`](/reference/headers/HX-Replace-Url)      | replaces the current URL in the location bar                                                                                                                                       |
| [`HX-Reswap`](/reference/headers/HX-Reswap)                                      | allows you to specify how the response will be swapped. See [`hx-swap`](/reference/attributes/hx-swap) for possible values                                                                     |
| [`HX-Retarget`](/reference/headers/HX-Retarget)                                    | a CSS selector that updates the target of the content update to a different element on the page                                                                                    |
| [`HX-Reselect`](/reference/headers/HX-Reselect)                                    | a CSS selector that allows you to choose which part of the response is used to be swapped in. Overrides an existing [`hx-select`](/reference/attributes/hx-select) on the triggering element |
| [`HX-Trigger`](/reference/headers/HX-Trigger)              | allows you to trigger client-side events                                                                                                                                           |

For more on the `HX-Trigger` headers, see [`HX-Trigger` Response Headers](/reference/headers/HX-Trigger).

Submitting a form via htmx has the benefit of no longer needing
the [Post/Redirect/Get Pattern](https://en.wikipedia.org/wiki/Post/Redirect/Get).
After successfully processing a POST request on the server, you don't need to return
a [HTTP 302 (Redirect)](https://en.wikipedia.org/wiki/HTTP_302). You can directly return the new HTML fragment.

Also, the response headers above are not provided to htmx for processing with 3xx Redirect response codes
like [HTTP 302 (Redirect)](https://en.wikipedia.org/wiki/HTTP_302). Instead, the browser will intercept the redirection
internally and return the headers and response from the redirected URL. Where possible use alternative response codes
like `200` to allow returning of these response headers.

## Client-Side Scripting
<details class="warning">
<summary>Changes in htmx 4.0</summary>

htmx 4.0 changed event names significantly when compared with htmx 2.0, making them much more standardized.

See the full event mapping in the [Changes in htmx 4.0](#migration) document.

**Note:** All events now provide a consistent `ctx` object with request/response information.

</details>

While htmx encourages a hypermedia approach to building web applications, it offers many options for client scripting.
Scripting is included in the REST-ful description of web architecture,
see: [Code-On-Demand](https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm#sec_5_1_7). As much as is
feasible, we recommend a [hypermedia-friendly](/essays/hypermedia-friendly-scripting) approach to scripting in your web
application:

* [Respect HATEOAS](/essays/hypermedia-friendly-scripting#prime_directive)
* [Use events to communicate between components](/essays/hypermedia-friendly-scripting#events)
* [Use islands to isolate non-hypermedia components from the rest of your application](/essays/hypermedia-friendly-scripting#islands)
* [Consider inline scripting](/essays/hypermedia-friendly-scripting#inline)

The primary integration point between htmx and scripting solutions is the events that htmx sends and can respond to.

We have an entire chapter entitled ["Client-Side Scripting"](https://hypermedia.systems/client-side-scripting/) in [our
book](https://hypermedia.systems) that looks at how scripting can be integrated into your htmx-based application.

### Events

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

```javascript
document.body.addEventListener('htmx:after:init', function (evt) {
    myJavascriptLib.init(evt.detail.elt);
});
```

or, if you would prefer, you can use the following htmx helper:

```javascript
htmx.on("htmx:after:init", function (evt) {
    myJavascriptLib.init(evt.detail.elt);
});
```

The `htmx:after:process` event is fired every time an element is processed by htmx, and is effectively the equivalent
to the normal `load` event.

#### Initialize A 3rd Party Library With Events

Using the `htmx:after:process` event to initialize content is so common that htmx provides a helper function:

```javascript
htmx.onLoad(function (target) {
    myJavascriptLib.init(target);
});
```

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

#### Configure a Request With Events

You can handle the [`htmx:config:request`](/reference/events/htmx-config-request) event in order to modify an AJAX request
before it is issued:

```javascript
document.body.addEventListener('htmx:config:request', function (evt) {
    evt.detail.ctx.request.body.set('auth_token', getAuthToken()); // add a new parameter into the request
    evt.detail.ctx.request.headers['Authentication-Token'] = getAuthToken(); // add a new header into the request
});
```

Here we add a parameter and header to the request before it is sent.

### The `hx-on:*` Attributes

HTML allows the embedding of inline scripts via the [
`onevent` properties](https://developer.mozilla.org/en-US/docs/Web/Events/Event_handlers#using_onevent_properties),
such as `onClick`:

```html
<button onclick="alert('You clicked me!')">
    Click Me!
</button>
```

This feature allows scripting logic to be co-located with the HTML elements the logic applies to, giving good
[Locality of Behaviour (LoB)](/essays/locality-of-behaviour).

Unfortunately, HTML only allows `on*` attributes for a fixed
number of [specific DOM events](https://www.w3schools.com/tags/ref_eventattributes.asp) (e.g. `onclick`) and
doesn't provide a generalized mechanism for responding to arbitrary events on elements.

In order to address this shortcoming, htmx offers [`hx-on:*`](/reference/attributes/hx-on) attributes.

These attributes allow you to respond to any event in a manner that preserves the LoB of the standard `on*` properties,
and provide some nice quality of life improvements over the standard javascript API.

If you want to respond to the `click` event using an `hx-on` attribute, we would write this:

```html
<button hx-on:click="alert('You clicked me!')">
    Click Me!
</button>
```

So, the string `hx-on`, followed by a colon (or a dash), then by the name of the event.

#### The Scripting API

htmx provides some top level helper methods in `hx-on` handlers that make async scripting more enjoyable:

| function    | description                                                                                                                          |
|-------------|--------------------------------------------------------------------------------------------------------------------------------------|
| `find()`    | allows you to find content relative to the current element (e.g. `find('next div')` will find the next div after the current element |
| `findAll()` | allows you to find multiple elements relative to the current element                                                                 |
| `timeout()` | allows you to wait for a given amount of time (e.g. `await timeout(100)` before continuing                                           |

#### Scripting Examples

Here is an example that adds a parameter to an htmx request

```html
<button hx-post="/example"
        hx-on:htmx:config:request="ctx.request.body.set('example', 'Hello Scripting!')">
    Post Me!
</button>
```

Here the `example` parameter is added to the `POST` request before it is issued, with the value 'Hello Scripting!'.

Another use case is to [reset user input](/patterns/reset-on-submit) on successful requests using the [`htmx:after:swap`](/reference/events/htmx-after-swap)
event:

```html
<button hx-post="/example"
        hx-on:htmx:after:request="find('closest form').reset()">
    Post Me!
</button>
```

### 3rd Party Javascript

Htmx integrates 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](/patterns/drag-to-reorder):

```html
<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>
</form>
```

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

In htmx, the cleanest way to do this is using the `htmx.onLoad()` method to register a callback.

This callback will be called whenever htmx inserts new content into the DOM, allowing you to initialize
any widgets in the new content.

```js
htmx.onLoad((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.

### See Also

- [`hx-on`](/reference/attributes/hx-on) (attribute)
- [Hypermedia-Friendly Scripting](/essays/hypermedia-friendly-scripting) (essay)
- [Locality of Behaviour](/essays/locality-of-behaviour) (essay)

## Multi-Target Updates

### The Problem

htmx requests normally update one target element. Sometimes you need to update multiple parts of the page at once.

For example: After submitting a form, you want to update both the form itself and a notification counter.

### The Solution

htmx provides two ways to update multiple targets from a single response:

1. **Out-of-Band Swaps** - Match elements by their `id` attribute
2. **Partial Tags** - Explicitly specify where content goes

Choose the method that fits your needs.

### Out-of-Band Swaps

Use out-of-band swaps when you want to match elements by their `id`.

Add [`hx-swap-oob`](/reference/attributes/hx-swap-oob)`="true"` to any element in your response. htmx will find the element with the same `id` in your page and swap it.

**Server response:**

```html
<div id="message" hx-swap-oob="true">
    Form submitted successfully!
</div>

<form id="my-form">
    <!-- Updated form content -->
</form>
```

**Result:**
- The `div#message` updates wherever it exists in your page
- The form updates in its normal target location

#### Customize the Swap

Specify a different swap style:

```html
<div id="notifications" hx-swap-oob="beforeend">
    <span>New notification</span>
</div>
```

This appends the content to `div#notifications` instead of replacing it.

#### Target a Different Element

Override the `id` matching by specifying a custom target:

```html
<div hx-swap-oob="innerHTML:#status">
    Processing...
</div>
```

This swaps the content into the element matching `#status`, regardless of the element's own `id`.

#### When to Use Out-of-Band Swaps

Use out-of-band swaps when:
- Elements have consistent, unique `id` attributes
- You want simple, ID-based updates
- You're updating notification areas, counters, or status indicators

### Partials (`<hx-partial>`)

Use partials when you need explicit control over targeting.

Wrap content in `<hx-partial>` tags. Specify where it goes with [`hx-target`](/reference/attributes/hx-target).

**Server response:**

```html
<hx-partial hx-target="#messages" hx-swap="beforeend">
    <div class="message">New message content</div>
</hx-partial>

<hx-partial hx-target="#notifications">
    <span class="badge">5</span>
</hx-partial>

<form id="my-form">
    <!-- Main form content -->
</form>
```

**Result:**
- First partial's content appends to `#messages`
- Second partial's content replaces contents of `#notifications`
- Form updates in its normal target location

#### Attributes

Each `<hx-partial>` accepts:

- [`hx-target`](/reference/attributes/hx-target) - CSS selector for where to place content
- `id` - Shorthand alternative to `hx-target`. Targets the element with that ID (e.g. `<hx-partial id="messages">` targets `#messages`)
- [`hx-swap`](/reference/attributes/hx-swap) - Optional. Swap style (defaults to `innerHTML`)

Either `hx-target` or `id` is required. If both are present, `hx-target` takes precedence.

<details>
<summary>Alternative syntax for template languages that strip unknown tags</summary>
You can use the equivalent <code>&lt;template&gt;</code> form: <code>&lt;template hx type="partial" hx-target="..." hx-swap="..."&gt;</code>. htmx converts <code>&lt;hx-partial&gt;</code> to this form internally.
</details>

#### Empty Response Behaviour

When a response contains only `<hx-partial>` elements and no main content, htmx will **not** perform the main swap.
This is the opposite default to [`hx-swap-oob`](/reference/attributes/hx-swap-oob): with partials, an
all-partial response signals intent — the server is explicitly routing multiple targeted updates and there is no main
content to swap.

```html
<!-- Server returns only partials — main target is left untouched -->
<hx-partial hx-target="#notifications">
    <span class="badge">5</span>
</hx-partial>
<hx-partial hx-target="#messages" hx-swap="beforeend">
    <div class="message">New message</div>
</hx-partial>
```

If you also want the main target cleared, add `swapEmpty:true` to `hx-swap` on the triggering element:

```html
<button hx-post="/submit" hx-swap="outerHTML swapEmpty:true">Submit</button>
```

Or set the global default via [`htmx.config.defaultSwapEmpty`](/reference/config/htmx-config-defaultSwapEmpty).

#### When to Use Partials

Use partials when:
- Elements don't have `id` attributes
- You need to target by class or other selectors
- You want explicit control over what goes where
- You're building more complex update patterns

### Choosing Between Them

Both methods work together. Use them in the same response if needed.

**Use [`hx-swap-oob`](/reference/attributes/hx-swap-oob)** for simple ID-based updates.

**Use [`<hx-partial>`](/reference/tags/hx-partial)** for everything else.

### Additional Features

#### Select Specific Elements for OOB

Use [`hx-select-oob`](/reference/attributes/hx-select-oob) on the triggering element to extract specific elements from the response for out-of-band swapping:

```html
<button hx-post="/submit"
        hx-target="#form"
        hx-select-oob="#message, #counter">
    Submit
</button>
```

This pulls `#message` and `#counter` from the response and swaps them out-of-band, even if they don't have `hx-swap-oob` attributes.

#### Preserve Content During Swaps

Add [`hx-preserve`](/reference/attributes/hx-preserve)`="true"` to elements you want to keep across swaps:

```html
<video id="my-video" hx-preserve="true">
    <source src="video.mp4">
</video>
```

This keeps the video playing even when the parent container gets updated.

## HCON
HCON (htmx Configuration Object Notation) is htmx's mini config language for structured HTML attributes.

```html
<meta name="htmx-config" content="transitions defaultTimeout:5000 sse.reconnect:true">
```

The equivalent JSON form works too:

```html
<meta name="htmx-config" content='{"transitions":true,"defaultTimeout":5000,"sse":{"reconnect":true}}'>
```

HCON drops the outer braces and accepts flag-style booleans, dotted paths, flexible quoting, and space-separated pairs.

You encounter HCON any time htmx reads a structured attribute:

- `hx-swap` modifiers: `innerHTML swap:200ms settle:100ms`
- `hx-trigger` modifiers: `click delay:500ms throttle:1s`
- `hx-config`: `credentials:"include" timeout:5000`
- `hx-vals` / `hx-headers`: `token:"abc" retry:3`
- `<meta name="htmx-config">`
- `HX-Location` response header

---

### Syntax

#### Key-value pairs

Pairs are separated by spaces or commas. Both are equivalent.

```
key:value key2:value2
key:value, key2:value2
```

#### Value types

| Input | Parsed as |
|---|---|
| `true` / `false` | boolean |
| `42`, `0.5` | number |
| `"quoted string"` | string (double quotes) |
| `'quoted string'` | string (single quotes) |
| `bare-word` | string |
| *(no value)* | `true` |

```html
<!-- booleans -->
<button hx-get="/api" hx-config="validate">          <!-- validate: true -->
<button hx-get="/api" hx-config="validate:false">    <!-- validate: false -->

<!-- numbers -->
<button hx-get="/api" hx-config="timeout:5000">

<!-- strings, quotes needed when value contains spaces or special chars -->
<button hx-get="/api" hx-config='credentials:"include"'>
<button hx-get="/api" hx-config="cache:'no-cache'">
```

#### Dot-notation for nested keys

Use `.` to set nested object properties:

```html
<meta name="htmx-config" content="sse.reconnect:true sse.reconnectDelay:1000">
```

This produces `{ sse: { reconnect: true, reconnectDelay: 1000 } }`.

#### JSON fallback

Any value starting with `{` is parsed as JSON instead of HCON. This lets you compose config server-side and inject it directly:

```html
<!-- server renders this -->
<meta name="htmx-config" content='{"defaultSwap":"outerHTML","transitions":true}'>

<!-- or on an element -->
<button hx-get="/api" hx-config='{"credentials":"include","timeout":5000}'>
```

JSON and HCON are not mixed, the entire string is one or the other.

#### Escaping commas and special characters

Wrap values in quotes when they contain commas, spaces, or HCON delimiters.

```html
<!-- comma ends the trigger spec -->
<input hx-trigger="keyup from:.a, .b">

<!-- quoted, comma is part of the value -->
<input hx-trigger='keyup from:".a, .b"'>

<!-- space ends the bare value -->
<button hx-vals="message:hello world">

<!-- quoted, the whole phrase is the value -->
<button hx-vals='message:"hello world"'>
```

Use whichever quote style the HTML attribute is not using:

```html
<input hx-trigger="keyup from:'.a, .b'">
<input hx-trigger='keyup from:".a, .b"'>
```

---

### Where HCON is used

#### `hx-swap` modifiers

The swap style comes first (not HCON), then modifiers are parsed as HCON:

```html
<div hx-get="/update" hx-swap="innerHTML swap:200ms settle:100ms scroll:top">
<div hx-get="/update" hx-swap="outerHTML transition:true ignoreTitle:true">
```

The full JSON form is also accepted. When using JSON, omitting `"style"` falls back to `config.defaultSwap` as expected; including it overrides the style:

```html
<!-- modifiers only, style = config.defaultSwap -->
<div hx-swap='{"swap":"200ms","settle":"100ms"}'>

<!-- explicit style -->
<div hx-swap='{"style":"outerHTML","swap":"200ms"}'>
```

Available swap modifiers: `swap`, `settle`, `scroll`, `show`, `scrollTarget`, `showTarget`,
`transition`, `strip`, `ignoreTitle`, `focusScroll`, `target`.

#### `hx-trigger` modifiers

The event name comes first, then modifiers:

```html
<input hx-get="/search" hx-trigger="keyup delay:300ms">
<button hx-post="/save" hx-trigger="click throttle:1s">
<div hx-get="/poll" hx-trigger="every 2s">
<form hx-post="/submit" hx-trigger="submit once">
```

Available trigger modifiers: `delay`, `throttle`, `from`, `target`, `consume`, `changed`, `once`.

#### `hx-config`

Merges into the request context before the request is issued. Useful for per-element fetch options:

```html
<button hx-get="/slow" hx-config="timeout:30000">
<button hx-get="/api/data" hx-config='credentials:"include" cache:"no-cache"'>
```

#### `<meta name="htmx-config">`

Sets global `htmx.config` values. Accepts HCON or JSON:

```html
<!-- HCON -->
<meta name="htmx-config" content="defaultSwap:outerHTML transitions:true">

<!-- JSON -->
<meta name="htmx-config" content='{"defaultSwap":"outerHTML","transitions":true}'>

<!-- nested via dot notation -->
<meta name="htmx-config" content="sse.reconnect:true sse.reconnectMaxAttempts:5">
```

#### `HX-Location` response header

The server can return HCON or JSON in this header:

```
HX-Location: /new-page
HX-Location: path:"/new-page" push:"true"
HX-Location: {"path":"/new-page","push":"true"}
```

---

### Attribute inheritance and `:append`

HCON itself is a parsing format. The `:append` composition feature lives one level up, in htmx's attribute inheritance system. When you use `:append` on an attribute name, htmx merges the child value with the inherited parent value by concatenating them (stripping `{}`):

```html
<div hx-headers:inherited='{"X-Tenant": "acme"}'>
  <button hx-get="/api"
          hx-headers:append='{"X-Request-ID": "123"}'>
    <!-- sends both headers -->
  </button>
</div>
```

This works for any attribute that accepts HCON/JSON, including `hx-vals` and `hx-headers`.

---

### Notes and limitations

- **Use JSON literals for primitive types.** `count:42` becomes a number, `enabled:true` becomes a boolean, and `name:bob` stays a string.
- **Quote values for grouping, not type control.** Quotes preserve spaces, commas, and delimiters. Quoted numeric and boolean values still parse as numbers and booleans.
- **Write durations naturally.** `200ms`, `2s`, and `1m` stay strings in HCON; htmx converts them where durations are expected.
- **Quote dotted keys to keep them literal.** `"a.b":1` produces `{"a.b": 1}`. Bare `a.b:1` produces `{a: {b: 1}}`.
- **Quote spaces and commas.** HCON-aware comma splitting preserves commas inside `[...]`, `(...)`, `<.../>`, and quotes.
- **Use JSON for arrays.** HCON has no array literal syntax, so use JSON fallback, such as `{"items":[1,2,3]}`.
- **Use dot-notation for nested objects.** `a.b.c:value` works; `a:{b:{c:value}}` does not.
- **Use `js:` for expressions.** HCON values are literals, so write `hx-vals="js:{token: getToken()}"` for dynamic values.
- **Avoid prototype keys.** `__proto__`, `constructor`, and `prototype` are ignored.
- **Choose JSON or HCON.** A string starting with `{` is JSON; the two syntaxes cannot be mixed.

### See also

- [`hx-swap`](/reference/attributes/hx-swap)
- [`hx-trigger`](/reference/attributes/hx-trigger)
- [`hx-vals`](/reference/attributes/hx-vals)
- [`hx-config`](/reference/attributes/hx-config)

<hr data-sidebar-group="Features" />

## CSS Transitions

htmx makes it easy to
use [CSS Transitions](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Transitions/Using_CSS_transitions) without
javascript. Consider this HTML content:

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

Imagine this content is replaced by htmx via an ajax request with this new content:

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

Note two things:

* The div has the *same* id in the original and in the new content
* The `red` class has been added to the new content

Given this situation, we can write a CSS transition from the old state to the new state:

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

When htmx swaps in this new content, it will do so in such a way that the CSS transition will apply to the new content,
giving you a nice, smooth transition to the new state.

So, in summary, all you need to do to use CSS transitions for an element is keep its `id` stable across requests!

## Synchronization

Often you want to coordinate the requests between two elements. For example, you may want a request from one element
to supersede the request of another element, or to wait until the other element's request has finished.

htmx offers a [`hx-sync`](/reference/attributes/hx-sync) attribute to help you accomplish this.

Consider a race condition between a form submission and an individual input's validation request in this HTML:

```html
<form hx-post="/store">
    <input id="title" name="title" type="text"
           hx-post="/validate"
           hx-trigger="change">
    <button type="submit">Submit</button>
</form>
```

Without using `hx-sync`, filling out the input and immediately submitting the form triggers two parallel requests to
`/validate` and `/store`.

Using `hx-sync="closest form"` on the input and `hx-sync="this:replace"` on the form will watch for requests from the
form
and abort an input's in flight request:

```html
<form hx-post="/store" hx-sync="this:replace">
    <input id="title" name="title" type="text"
           hx-post="/validate"
           hx-trigger="change"
           hx-sync="closest form">
    <button type="submit">Submit</button>
</form>
```

This resolves the synchronization between the two elements in a declarative way.

htmx also supports a programmatic way to cancel requests: you can send the [`htmx:abort`](/reference/events/htmx-abort) event to an element to
cancel any in-flight requests:

```html
<button id="request-button" hx-post="/example">
    Issue Request
</button>
<button onclick="htmx.trigger('#request-button', 'htmx:abort')">
    Cancel Request
</button>
```

More examples and details can be found on the [`hx-sync` attribute page.](/reference/attributes/hx-sync)

## Confirmations

Often you will want to confirm an action before issuing a request. htmx supports the [`hx-confirm`](/reference/attributes/hx-confirm)
attribute, which allows you to confirm an action using a simple javascript dialog:

```html
<button hx-delete="/account" hx-confirm="Are you sure you wish to delete your account?">
    Delete My Account
</button>
```

`hx-confirm` may also contain JavaScript by using the `js:` or `javascript:` prefix. In this case
the JavaScript will be evaluated and, if a promise is returned, it will wait until the promise
resolves with a `true` value to continue

```html

<script>
    async function swalConfirm() {
        let result = await Swal.fire({
            title: "Are you sure?",
            text: "You won't be able to revert this!",
            icon: "warning",
            showCancelButton: true,
            confirmButtonColor: "#3085d6",
            cancelButtonColor: "#d33",
            confirmButtonText: "Yes, delete it!"
        })
        return result.isConfirmed
    }
</script>
<button hx-delete="/account" hx-confirm="js:swalConfirm()">
    Delete My Account
</button>
```

## Boosting

Htmx supports "boosting" regular HTML anchors and forms with the [`hx-boost`](/reference/attributes/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:

```html
<div hx-boost:inherited="true">
    <a href="/blog">Blog</a>
    <a href="/about">About</a>
    <a href="/contact">Contact</a>
</div>
```

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

Note that `hx-boost` is using the `inherited` modifier here.

### Progressive Enhancement 

A nice feature of `hx-boost` is that it degrades gracefully if javascript is not enabled: the links and forms continue
to work, they simply don't use ajax requests.

This is known as
[Progressive Enhancement](https://developer.mozilla.org/en-US/docs/Glossary/Progressive_Enhancement), and it allows
a wider audience to use your site's functionality.

Other htmx patterns can be adapted to achieve progressive enhancement as well, but they will require more thought.

Consider the [active search](/patterns/active-search) example. As it is written, it will not degrade gracefully:
someone who does not have javascript enabled will not be able to use this feature. This is done for simplicity's sake,
to keep the example as brief as possible.

However, you could wrap the htmx-enhanced input in a form element:

```html
<form action="/search" method="POST">
    <input class="form-control" type="search"
           name="search" placeholder="Begin typing to search users..."
           hx-post="/search"
           hx-trigger="keyup changed delay:500ms, search"
           hx-target="#search-results"
           hx-indicator=".htmx-indicator">
</form>
```

With this in place, javascript-enabled clients would still get the nice active-search UX, but non-javascript enabled
clients would be able to hit the enter key and still search. Even better, you could add a "Search" button as well.
You would then need to update the form with an [`hx-post`](/reference/attributes/hx-post) that mirrored the `action` attribute, or perhaps use `hx-boost`
on it.

You would need to check on the server side for the [`HX-Request`](/reference/headers/HX-Request) header to differentiate between an htmx-driven and a
regular request, to determine exactly what to render to the client.

Other patterns can be adapted similarly to achieve the progressive enhancement needs of your application.

As you can see, this requires more thought and more work. It also rules some functionality entirely out of bounds.
These tradeoffs must be made by you, the developer, with respect to your projects goals and audience.

[Accessibility](https://developer.mozilla.org/en-US/docs/Learn/Accessibility/What_is_accessibility) is a concept
closely related to progressive enhancement. Using progressive enhancement techniques such as `hx-boost` will make your
htmx application more accessible to a wide array of users.

htmx-based applications are very similar to normal, non-AJAX driven web applications because htmx is HTML-oriented.

As such, the normal HTML accessibility recommendations apply. For example:

* Use semantic HTML as much as possible (i.e. the right tags for the right things)
* Ensure focus state is clearly visible
* Associate text labels with all form fields
* Maximize the readability of your application with appropriate fonts, contrast, etc.

## History

<details class="warning">
<summary>Changes in htmx 4.0</summary>

History support in htmx 4.0 has changed significantly. We no longer snapshot the DOM and keep a copy in sessionStorage.

Instead, we issue a full page request every time someone navigates to a history element. This is much less error-prone
and foolproof. It also eliminates security concerns regarding keeping history state in accessible storage

This change makes history restoration much more reliable and reduces client-side complexity.

</details>

Htmx provides a simple mechanism for interacting with
the [browser history API](https://developer.mozilla.org/en-US/docs/Web/API/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`](/reference/attributes/hx-push-url) attribute:

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

When a user clicks on this link, htmx will push a new location onto the history stack.

When a user hits the back button, htmx will retrieve the old content from the original URL and swap it back into the
body,
simulating "going back" to the previous state.

**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.

## Validation

Htmx integrates with the [HTML5 Validation API](https://developer.mozilla.org/en-US/docs/Learn/Forms/Form_validation)
and will not issue a request for a form if a validatable input is invalid.

Non-form elements do not validate before they make requests by default, but you can enable validation by setting
the [`hx-validate`](/reference/attributes/hx-validate) attribute to "true".

## Web Components

htmx doesn't automatically scan inside web components' shadow DOM. You must manually initialize it.

After creating your shadow DOM, call [`htmx.process`](/reference/methods/htmx-process):

```javascript
customElements.define('my-counter', class extends HTMLElement {
    connectedCallback() {
        const shadow = this.attachShadow({mode: 'open'})
        shadow.innerHTML = `
          <button hx-post="/increment" hx-target="#count">+1</button>
          <div id="count">0</div>
        `
        htmx.process(shadow) // Initialize htmx for this shadow DOM
    }
})

```

#### Targeting Elements Outside Shadow DOM

Selectors like [`hx-target`](/reference/attributes/hx-target) only see elements inside the same shadow DOM.

To break out:

1. Target the host element, using `host`:

```html
<button hx-get="..." hx-target="host">
  ...
</button>
```

2. Target elements in main document, using `global:<selector>`:

```html
<button hx-get="..." hx-target="global:#target">
  ...
</button>
```

#### Components Without Shadow DOM

Still call [`htmx.process`](/reference/methods/htmx-process) on the component:

```javascript
customElements.define('simple-widget', class extends HTMLElement {
    connectedCallback() {
        this.innerHTML = `Load`
        htmx.process(this)
    }
})
```

## Attribute Inheritance

<details class="warning">
<summary>Changes in htmx 4.0</summary>

In htmx 2.0 attribute inheritance was implicit by default: elements inherited the attributes on their parents, such
as [`hx-target`](/reference/attributes/hx-target). In htmx 4.0 attribute inheritance is now explicit by default, using the `:inherited` modifier.

</details>

Inheritance allows you to "hoist" attributes up the DOM to avoid code duplication.

Consider the following htmx:

```html
<button hx-delete="/account" hx-confirm="Are you sure?">
    Delete My Account
</button>
<button hx-put="/account" hx-confirm="Are you sure?">
    Update My Account
</button>
```

Here we have a duplicate [`hx-confirm`](/reference/attributes/hx-confirm) attribute.

We can hoist this attribute to a parent element using the `:inherited` modifier:

```html
<div hx-confirm:inherited="Are you sure?">
    <button hx-delete="/account">
        Delete My Account
    </button>
    <button hx-put="/account">
        Update My Account
    </button>
</div>
```

This `hx-confirm` attribute will now apply to all htmx-powered elements within it.

## Extended Selectors
Extended selectors let you target elements in flexible ways.

Use them with [`hx-target`](/reference/attributes/hx-target), [`hx-sync`](/reference/attributes/hx-sync), and other attributes that accept selectors.

### Standard CSS Selectors

Start with any CSS selector.

```html
<!-- Target by ID -->
<button hx-get="/data" hx-target="#results">Load</button>

<!-- Target by class -->
<button hx-get="/data" hx-target=".container">Load</button>

<!-- Target by attribute -->
<button hx-get="/data" hx-target="[data-results]">Load</button>
```

### `this`

Target the element itself.

```html
<!-- Update the button when clicked -->
<button hx-get="/status" hx-target="this">Check Status</button>
```

The button will replace itself with the response.

### `closest <selector>`

Find the nearest ancestor matching the selector.

Searches upward through parent elements.

```html
<div class="card">
  <div class="card-body">
    <button hx-get="/refresh" hx-target="closest .card">Refresh</button>
  </div>
</div>
```

The button targets its parent `.card` element.

Works like [Element.closest()](https://developer.mozilla.org/docs/Web/API/Element/closest).

### `find <selector>`

Find the first child matching the selector.

Searches downward through descendant elements.

```html
<div hx-get="/user" hx-target="find .username">
  <span class="username">Loading...</span>
</div>
```

The div targets its child `.username` element.

Works like [Element.querySelector()](https://developer.mozilla.org/docs/Web/API/Element/querySelector).

### `findAll <selector>`

Find all children matching the selector.

```html
<div hx-get="/items" hx-target="findAll .item">
  <div class="item">Item 1</div>
  <div class="item">Item 2</div>
</div>
```

Targets all `.item` elements inside the div.

Works like [Element.querySelectorAll()](https://developer.mozilla.org/docs/Web/API/Element/querySelectorAll).

### `next`

Target the next sibling element.

```html
<button hx-get="/more" hx-target="next">Load More</button>
<div>Content loads here</div>
```

Targets the element immediately after the button.

Works like [`Element.nextElementSibling`](https://developer.mozilla.org/docs/Web/API/Element/nextElementSibling).

### `next <selector>`

Scan forward for the first matching element.

Searches through all following siblings.

```html
<button hx-get="/data" hx-target="next .results">Load</button>
<div class="other">Not here</div>
<div class="results">Loads here</div>
```

Skips siblings until it finds `.results`.

### `previous`

Target the previous sibling element.

```html
<div>Content loads here</div>
<button hx-get="/more" hx-target="previous">Load More</button>
```

Targets the element immediately before the button.

Works like [Element.previousElementSibling](https://developer.mozilla.org/docs/Web/API/Element/previousElementSibling).

### `previous <selector>`

Scan backward for the first matching element.

Searches through all preceding siblings.

```html
<div class="results">Loads here</div>
<div class="other">Not here</div>
<button hx-get="/data" hx-target="previous .results">Load</button>
```

Skips siblings until it finds `.results`.

### Special Keywords

#### `body`

Target the document body.

```html
<button hx-get="/page" hx-target="body">Load Page</button>
```

Useful for full-page updates.

#### `document`

Reference the entire document.

```html
<div hx-trigger="click from:document">...</div>
```

Used primarily with event triggers.

#### `window`

Reference the window object.

```html
<div hx-trigger="scroll from:window">...</div>
```

Used primarily with window events.

#### `host`

Target the shadow DOM host element.

```html
<!-- Inside shadow DOM -->
<button hx-get="/data" hx-target="host">Update Host</button>
```

Only works inside shadow DOM.

### `global <selector>`

Search the entire document tree.

By default, selectors search within the current shadow DOM boundary.

```html
<!-- Inside shadow DOM -->
<button hx-get="/data" hx-target="global #results">Load</button>

<!-- Targets #results in the main document -->
<div id="results"></div>
```

Crosses shadow DOM boundaries.

### Multiple Targets

`hx-target` only resolves to a single element. To swap the same response into multiple elements, use the [`hx-targets`](/extensions/hx-targets) extension attribute:

```html
<script src="https://cdn.jsdelivr.net/npm/htmx.org@__VERSION__/dist/ext/hx-targets.js"></script>

<button hx-get="/data" hx-targets="#results, #cache">Load</button>
```

See the [hx-targets extension docs](/extensions/hx-targets) for full details.

### Hyperscript-Style Syntax

Wrap selectors in `<.../>` for hyperscript compatibility.

```html
<button hx-get="/data" hx-target="<#results/>">Load</button>
```

This mimics [hyperscript query literals](https://hyperscript.org/expressions/query-reference/).

Useful if you're using hyperscript alongside htmx.

### Common Patterns

#### Update parent card

```html
<div class="card">
  <button hx-delete="/item/1" hx-target="closest .card">Delete</button>
</div>
```

#### Update sibling container

```html
<button hx-get="/data" hx-target="next .results">Load</button>
<div class="results"></div>
```

#### Update self

```html
<div hx-get="/refresh" hx-target="this">Click to refresh</div>
```

#### Update child element

```html
<div hx-get="/user" hx-target="find .username">
  <span class="username">Loading...</span>
</div>
```

### Notes

* Selectors are evaluated once when htmx processes the element. They are not re-evaluated when the page changes.

### Tips

Start with simple CSS selectors.

Use `this` for self-updates.

Use `closest` to update parent containers.

Use `find` to update child elements.

Use `next` and `previous` for sibling relationships.

Avoid `id` attributes when relative selectors work.

This keeps your HTML cleaner.

## Extension System

htmx supports extensions to augment its core hypermedia infrastructure. The extension mechanism takes pressure off the core library to add new features, allowing it to focus on its main purpose of generalizing hypermedia controls.

For the catalog of core extensions shipped with htmx, see [/extensions](/extensions).

### Using Extensions

In htmx 4, extensions hook into standard events rather than callback extension points. They are lightweight with no performance penalty.

Extensions apply page-wide without requiring `hx-ext` on parent elements. They activate via custom attributes where needed.

#### Loading an Extension

Include the extension script after htmx. Core extensions ship with htmx in the `/ext/` directory:

```html
<script src="https://cdn.jsdelivr.net/npm/htmx.org@__VERSION__/dist/htmx.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/htmx.org@__VERSION__/dist/ext/hx-sse.js"></script>
```

Or with a bundler:

```javascript
import 'htmx.org';
import 'htmx.org/dist/ext/hx-sse';
```

#### Restricting Extensions

To restrict which extensions can register, use an allow list:

```html
<meta name="htmx-config" content='{"extensions": "my-ext,another-ext"}'>
```

When this config is set, only the listed extensions will be loaded. Without it, all registered extensions are active.

### Building Extensions

htmx 4 introduces an extension system based on event hooks rather than the old callback-based API.

#### Defining an Extension

Extensions are defined using `htmx.registerExtension()`:

```javascript
htmx.registerExtension("my-ext", {
    init: (internalAPI) => {
        // Called once when extension is registered
        // Store internalAPI reference if needed
    },

    htmx_before_request: (elt, detail) => {
        // Called before each request
        // Return false to cancel
    },

    htmx_after_request: (elt, detail) => {
        // Called after each request
    },
});
```

#### Event Hooks

Extensions hook into htmx lifecycle events. Event names use underscores instead of colons:

##### Core Lifecycle Events

| Hook Name | Triggered Event | Parameters | Description |
|-----------|----------------|------------|-------------|
| `htmx_before_init` | [`htmx:before:init`](/reference/events/htmx-before-init) | `(elt, detail)` | Before element initialization |
| `htmx_after_init` | [`htmx:after:init`](/reference/events/htmx-after-init) | `(elt, detail)` | After element initialization |
| `htmx_before_process` | [`htmx:before:process`](/reference/events/htmx-before-process) | `(elt, detail)` | Before processing element |
| `htmx_after_process` | [`htmx:after:process`](/reference/events/htmx-after-process) | `(elt, detail)` | After processing element |
| `htmx_before_cleanup` | [`htmx:before:cleanup`](/reference/events/htmx-before-cleanup) | `(elt, detail)` | Before cleaning up element |
| `htmx_after_cleanup` | [`htmx:after:cleanup`](/reference/events/htmx-after-cleanup) | `(elt, detail)` | After cleaning up element |

##### Request Lifecycle Events

| Hook Name | Triggered Event | Parameters | Description |
|-----------|----------------|------------|-------------|
| `htmx_config_request` | [`htmx:config:request`](/reference/events/htmx-config-request) | `(elt, detail)` | Configure request before sending |
| `htmx_before_request` | [`htmx:before:request`](/reference/events/htmx-before-request) | `(elt, detail)` | Before request is sent |
| `htmx_before_response` | `htmx:before:response` | `(elt, detail)` | After fetch, before body consumed |
| `htmx_after_request` | [`htmx:after:request`](/reference/events/htmx-after-request) | `(elt, detail)` | After request completes |
| `htmx_finally_request` | [`htmx:finally:request`](/reference/events/htmx-finally-request) | `(elt, detail)` | When request completes, fails, or is cancelled |
| `htmx_error` | [`htmx:error`](/reference/events/htmx-error) | `(elt, detail)` | On request error |

##### Swap Events

| Hook Name | Triggered Event | Parameters | Description |
|-----------|----------------|------------|-------------|
| `htmx_before_swap` | [`htmx:before:swap`](/reference/events/htmx-before-swap) | `(elt, detail)` | Before content swap |
| `htmx_after_swap` | [`htmx:after:swap`](/reference/events/htmx-after-swap) | `(elt, detail)` | After content swap |
| `htmx_finally_swap` | [`htmx:finally:swap`](/reference/events/htmx-finally-swap) | `(elt, detail)` | After swap (success or error) |
| `htmx_before_settle` | `htmx:before:settle` | `(elt, detail)` | Before settle phase |
| `htmx_after_settle` | `htmx:after:settle` | `(elt, detail)` | After settle phase |
| `handle_swap` | _(direct call)_ | `(swapStyle, target, fragment, swapSpec)` | Custom swap handler |

##### History Events

| Hook Name | Triggered Event | Parameters | Description |
|-----------|----------------|------------|-------------|
| `htmx_before_history_update` | [`htmx:before:history:update`](/reference/events/htmx-before-history-update) | `(elt, detail)` | Before updating history |
| `htmx_after_history_update` | [`htmx:after:history:update`](/reference/events/htmx-after-history-update) | `(elt, detail)` | After updating history |
| `htmx_after_history_push` | [`htmx:after:history:push`](/reference/events/htmx-after-push-into-history) | `(elt, detail)` | After pushing to history |
| `htmx_after_history_replace` | [`htmx:after:history:replace`](/reference/events/htmx-after-replace-into-history) | `(elt, detail)` | After replacing history |
| `htmx_before_history_restore` | [`htmx:before:history:restore`](/reference/events/htmx-before-restore-history) | `(elt, detail)` | Before restoring from history |

#### Cancelling Events

Return `false` or set `detail.cancelled = true` to cancel an event:

```javascript
htmx.registerExtension("validator", {
    htmx_before_request: (elt, detail) => {
        if (!isValid(detail.ctx)) {
            return false; // Cancel request
        }
    },
});
```

#### Internal API

The `init` hook receives an internal API object with helper methods:

```javascript
let api;

htmx.registerExtension("my-ext", {
    init: (internalAPI) => {
        api = internalAPI;
    },

    htmx_after_init: (elt) => {
        let value = api.attributeValue(elt, "hx-my-attr");
        let specs = api.parseTriggerSpecs("click, keyup delay:500ms");
        let { method, action } = api.determineMethodAndAction(elt, evt);
    },
});
```

Available internal API:

{/* check_extension_api:start */}
- `HCON` - Parse and merge HCON values
- `attributeValue(elt, name, defaultVal, returnElt)` - Get an attribute value with inheritance support
- `parseTriggerSpecs(spec)` - Parse a trigger specification
- `determineMethodAndAction(elt, evt)` - Get `{method, action}` for an element
- `createRequestContext(elt, evt)` - Create a request context
- `collectFormData(elt, form, submitter, validate, isGet)` - Collect form data
- `getAttributeObject(elt, name, callback, scope)` - Read an object-valued attribute
- `insertContent(task, cssTransition)` - Insert a swap task's content
- `morph(oldNode, fragment, innerHTML)` - Morph existing content
- `isSoftMatch(oldNode, newNode)` - Test whether two nodes can be morphed
- `initSecurity(ttPolicy, syncFn, asyncFn)` - Configure Trusted Types and script constructors
- `onTrigger(elt, spec, handler)` - Attach a parsed trigger handler
- `htmxProp(elt)` - Get an element's internal htmx state
- `triggerHtmxEvent(elt, name, detail, bubbles)` - Dispatch an htmx event
- `executeJavaScript(thisArg, values, code, expression, isAsync)` - Execute JavaScript through htmx security policy
{/* check_extension_api:end */}

#### Request Context

The `detail.ctx` object contains request information:

```javascript
{
    sourceElement,      // Element triggering request
    sourceEvent,        // Event that triggered request
    status,            // Request status
    target,            // Target element for swap
    swap,              // Swap strategy
    request: {
        action,        // Request URL
        method,        // HTTP method
        headers,       // Request headers
        body,          // FormData during htmx_config_request; final BodyInit later
        validate,      // Whether to validate
        abort,         // Function to abort request
        signal         // AbortSignal
    },
    response: {        // Available after request
        raw,           // Raw Response object
        status,        // HTTP status code
        headers        // Response headers: https://developer.mozilla.org/en-US/docs/Web/API/Headers
    },
    text,              // Response text (after request)
    hx                 // HX-* response headers (parsed)
}
```

#### Custom Swap Strategies

Extensions can implement custom swap strategies:

```javascript
htmx.registerExtension("my-swap", {
    handle_swap: (swapStyle, target, fragment, swapSpec) => {
        if (swapStyle === "my-custom-swap") {
            target.appendChild(fragment);
            return true; // Handled
        }
        return false; // Not handled
    },
});
```

For migrating extensions written for htmx 2.x, see [Migration → Migrating Your Own Extensions](#migrating-your-own-extensions).

<hr data-sidebar-group="Security" />

## Best Practices

htmx allows you to define logic directly in your DOM. This has a number of advantages, the largest being
[Locality of Behavior](/essays/locality-of-behaviour), which makes your system easier to understand and
maintain.

A concern with this approach, however, is security: since htmx increases the expressiveness of HTML, if a malicious
user is able to inject HTML into your application, they can leverage this expressiveness of htmx to malicious
ends.

### Rule 1: Escape All User Content

The first rule of HTML-based web development has always been: *do not trust input from the user*. You should escape all
3rd party, untrusted content that is injected into your site. This is to prevent, among other issues,
[XSS attacks](https://en.wikipedia.org/wiki/Cross-site_scripting).

There is extensive documentation on XSS and how to prevent it on the
excellent [OWASP Website](https://owasp.org/www-community/attacks/xss/),
including
a [Cross Site Scripting Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html).

The good news is that this is a very old and well understood topic, and the vast majority of server-side templating
languages
support [automatic escaping](https://docs.djangoproject.com/en/4.2/ref/templates/language/#automatic-html-escaping) of
content to prevent just such an issue.

That being said, there are times people choose to inject HTML more dangerously, often via some sort of `raw()`
mechanism in their templating language. This can be done for good reasons, but if the content being injected is coming
from a 3rd party then it _must_ be scrubbed, including removing attributes starting with `hx-` and `data-hx`, as well as
inline `<script>` tags, etc.

If you are injecting raw HTML and doing your own escaping, a best practice is to *whitelist* the attributes and tags you
allow, rather than to blacklist the ones you disallow.

### htmx Security Tools

Of course, bugs happen and developers are not perfect, so it is good to have a layered approach to security for
your web application, and htmx provides tools to help secure your application as well.

Let's take a look at them.

#### `hx-ignore`

The first tool htmx provides to help further secure your application is the [`hx-ignore`](/reference/attributes/hx-ignore)
attribute. This attribute will prevent processing of all htmx attributes on a given element, and on all elements within
it. So, for example, if you were including raw HTML content in a template (again, this is not recommended!) then you
could place a div around the content with the `hx-ignore` attribute on it:

```html
<div hx-ignore>
    <%= raw(user_content) %>
</div>
```

And htmx will not process any htmx-related attributes or features found in that content. This attribute cannot be
disabled by injecting further content: if an `hx-ignore` attribute is found anywhere in the parent hierarchy of an
element, it will not be processed by htmx.

### CSP Options

Browsers also provide tools for further securing your web application. The most powerful tool available is a
[Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP). Using a CSP you can tell the
browser to, for example, not issue requests to non-origin hosts, to not evaluate inline script tags, etc.

CSP can be set via an HTTP header or a `<meta>` tag. HTTP headers are preferred, `<meta>` tags
do not enforce all directives and scripts that appear before the `<meta>` tag in the document are
not covered by it:

```http
Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-<nonce>'
```

A full discussion of CSPs is beyond the scope of this document, but
the [MDN Article](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) provides a good jumping-off point
for exploring this topic.

#### Controlling Cross-Origin Requests

htmx defaults [`htmx.config.mode`](/reference/config/htmx-config-mode) to `"same-origin"`, which causes the
browser to reject any cross-origin fetch, even if an attacker injects an `hx-get` pointing elsewhere.

This setting is enforced for markup: any `mode` value in a per-element `hx-config` attribute is ignored and reset to the global config value. Injected markup like `hx-config='{"mode":"cors"}'` cannot widen request scope.

If your application legitimately needs CORS (e.g. an API on a different subdomain):

1. Set the mode globally:
   ```javascript
   htmx.config.mode = "cors";
   ```
2. Lock down reachable origins with `connect-src`:
   ```html
   <meta http-equiv="Content-Security-Policy"
         content="connect-src 'self' https://api.example.com">
   ```

With both in place, htmx can reach your API but injected target URLs to other origins are blocked by CSP.

#### hx-csp Extension

For sites using CSP script nonces, the [`hx-csp` extension](/extensions/hx-csp) provides deep integration:

- Gates all htmx attribute processing behind a per-request nonce, blocking injected htmx attributes
- Automatically creates a `'htmx'` [Trusted Types](https://developer.mozilla.org/en-US/docs/Web/API/Trusted_Types_API) policy so only htmx can write HTML into DOM sinks
- Replaces `new Function()` eval with nonce-based script injection when `safeEval:true` is set, removing the need for `unsafe-eval`

See the [hx-csp extension docs](/extensions/hx-csp) for full setup instructions.

#### htmx & Eval

htmx uses `new Function()` for some optional features:

* Event filters
* The [`hx-on`](/reference/attributes/hx-on) attribute
* Attribute values starting with `js:` or `javascript:`

All of these are optional. If you don't use them you can omit `unsafe-eval` from your CSP entirely.

If you do use these features, the [`hx-csp` extension](/extensions/hx-csp) with `safeEval:true` replaces
`new Function()` with nonce-based script injection, enabling them without `unsafe-eval`.

#### CSP & Inline Styles

htmx injects its indicator CSS using [Constructable Stylesheets](https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleSheet/CSSStyleSheet) (`document.adoptedStyleSheets`), which are not subject to `style-src` CSP restrictions.

The one area to be aware of is morph swaps when used alongside JS frameworks like Alpine that set `style` attributes via JavaScript. During morph, htmx reconciles attributes between the old and new element, including any `style` value. Under a strict `style-src` policy without `'unsafe-inline'`, this `setAttribute("style", ...)` call will produce a CSP violation.

Add `"style"` to [`morphIgnore`](/reference/config/htmx-config-morphIgnore) to skip it:

```html
<meta name="htmx-config" content='{"morphIgnore":["data-htmx-powered","style"]}'>
```

Class-based CSS transitions continue to work normally.

### CSRF Prevention

The assignment and checking of CSRF tokens are typically backend responsibilities, but `htmx` can support returning the
CSRF token automatically with every request using the [`hx-headers`](/reference/attributes/hx-headers) attribute. The attribute needs to be added to the
element issuing the request or one of its ancestor elements. This makes the `html` and `body` elements effective
global vehicles for adding the CSRF token to the `HTTP` request header, as illustrated below.

```html
<html lang="en" hx-headers='{"X-CSRF-TOKEN": "CSRF_TOKEN_INSERTED_HERE"}'>
:
</html>
```

The above elements are usually unique in an HTML document and should be easy to locate within templates.

## Caching

htmx works with standard [HTTP caching](https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching)
mechanisms out of the box.

If your server adds the
[`Last-Modified`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Last-Modified)
HTTP response header to the response for a given URL, the browser will automatically add the
[`If-Modified-Since`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-Modified-Since)
request HTTP header to the next requests to the same URL.

For polling use cases where you want the server to skip responses when content hasn't changed, see the [`ptag` extension](/extensions/hx-ptag).

Be mindful that if your server can render different content for the same URL depending on some other
headers, you need to use the [`Vary`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching#vary)
response HTTP header.

For example, if your server renders the full HTML when the [`HX-Request`](/reference/headers/HX-Request) header is missing or `false`, and it renders a
fragment of that HTML when `HX-Request: true`, you need to add `Vary: HX-Request`. That causes the cache to be keyed
based on a composite of the response URL and the `HX-Request` request header rather than being based just on the
response URL.

<hr data-sidebar-group="Troubleshoot" />

## Debugging

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

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

Here are some tips:

Errors and warnings flow to `console.error` / `console.warn` by default. To also see every event htmx dispatches, set `htmx.config.logAll = true`:

```javascript
htmx.config.logAll = true;
```

Observability tools (Sentry, DataDog RUM, LogRocket, etc.) capture `console.*` automatically, so htmx logs flow into your existing pipeline without any extra setup.

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()`](https://developers.google.com/web/updates/2015/05/quickly-monitor-events-from-the-console-panel)
method available in the
browser console:

```javascript
monitorEvents(htmx.find("#theElement"));
```

This will spit out all events that are occurring 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.

You would most likely want to set a break point in the methods to see what's going on.

And always feel free to jump on the [Discord](https://htmx.org/discord) if you need help.

## Configuration

Htmx has configuration options that can be accessed either programmatically or declaratively.

They are listed below:

<div class="info-table">

| Config Variable                   | Info                                                                                                                                                                                                                                                                       |
|-----------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `htmx.config.logAll`              | defaults to `false`, if set to `true` htmx will log all events to the console for debugging                                                                                                                                                                                |
| `htmx.config.prefix`              | defaults to `"data-hx-"`, a secondary attribute prefix recognised alongside the always-active `hx-` prefix (e.g. `data-hx-get` works by default). Set to `""` to disable. **Must be set via meta tag**, setting this after page load will not apply correctly. |
| `htmx.config.transitions`         | defaults to `false`, whether to use view transitions when swapping content (if browser supports it)                                                                                                                                                                         |
| `htmx.config.history`             | defaults to `true`, whether to enable history support. Set to `"reload"` to do a full page reload on history navigation instead of an AJAX request                                                                                                                          |
| `htmx.config.mode`                | defaults to `'same-origin'`, the fetch mode for AJAX requests. Can be `'cors'`, `'no-cors'`, or `'same-origin'`                                                                                                                                                            |
| `htmx.config.defaultSwap`         | defaults to `innerHTML`                                                                                                                                                                                                                                                    |
| `htmx.config.indicatorClass`      | defaults to `htmx-indicator`                                                                                                                                                                                                                                               |
| `htmx.config.requestClass`        | defaults to `htmx-request`                                                                                                                                                                                                                                                 |
| `htmx.config.includeIndicatorCSS` | defaults to `true` (determines if the indicator styles are loaded)                                                                                                                                                                                                         |
| `htmx.config.defaultTimeout`      | defaults to `60000` (60 seconds), the number of milliseconds a request can take before automatically being terminated                                                                                                                                                      |
| `htmx.config.inlineScriptNonce`   | defaults to `''`, meaning that no nonce will be added to inline scripts                                                                                                                                                                                                    |
| `htmx.config.extensions`          | defaults to `''`, a comma-separated list of extension names to load (e.g., `'preload,optimistic'`)                                                                                                                                                                         |
| `htmx.config.morphIgnore`         | defaults to `["data-htmx-powered"]`, array of attribute name prefixes to preserve when morphing elements                                                                                                                                                                   |
| `htmx.config.morphScanLimit`      | limits the number of nodes scanned during morphing                                                                                                                                                                                                                         |
| `htmx.config.morphSkip`           | defaults to `'[hx-morph-skip]'`, CSS selector for elements to completely skip during morphing (they stay frozen)                                                                                                                                                            |
| `htmx.config.morphSkipChildren`   | defaults to `'[hx-morph-skip-children]'`, CSS selector for elements whose attributes update but children are preserved during morphing                                                                                                                                      |
| `htmx.config.noSwap`              | defaults to `[204, 304]`, array of HTTP status codes that should not trigger a swap                                                                                                                                                                                        |
| `htmx.config.implicitInheritance` | defaults to `false`, if set to `true` attributes will be inherited from parent elements automatically without requiring the `:inherited` modifier                                                                                                                          |
| `htmx.config.defaultFocusScroll`  | defaults to `false`, whether to scroll focused elements into view after swap                                                                                                                                                                                                |
| `htmx.config.defaultSettleDelay`  | defaults to `1` (ms), delay between swap and settle phases                                                                                                                                                                                                                 |
| `htmx.config.metaCharacter`       | defaults to `undefined`, allows you to use a custom character instead of `:` for attribute modifiers (e.g., `-` to use `hx-get-inherited` instead of `hx-get:inherited`)                                                                                                   |

</div>

You can set most options directly in JavaScript, or you can use a `meta` tag (accepts [HCON](#hcon) or JSON):

> **Note:** Some options are read only once during initialisation and must be set via the `meta` tag to take effect. These include `prefix`, `extensions`, and `metaCharacter`.

```html
<meta name="htmx-config" content='{"defaultSwap":"innerHTML"}'>
```

### Conclusion

And that's it!

Have fun with htmx!

You can accomplish [quite a bit](/patterns) without writing a lot of code!

<hr data-sidebar-group="Editor Support" />

## VS Code
The [HTMX Toolkit](https://marketplace.visualstudio.com/items?itemName=atoolz.htmx-vscode-toolkit) extension adds htmx support to Visual Studio Code with autocomplete, hover documentation, and snippets.

### Features

- Attribute autocomplete for all htmx attributes (`hx-get`, `hx-post`, `hx-target`, etc.)
- Hover documentation with links to the official docs
- Snippets for common htmx patterns
- Support for htmx 2.x and 4.x

### Installing

Search for **HTMX Toolkit** in the VS Code Extensions panel, or install from the [Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=atoolz.htmx-vscode-toolkit).

### Source

The extension source code is maintained at [atoolz/htmx-vscode-toolkit](https://github.com/atoolz/htmx-vscode-toolkit).

<style>{`[data-sidebar-group] { display: none; }`}</style>
