Aller au contenu

htmx Cheat Sheet

Overview

htmx is a small JavaScript library that allows you to access modern browser features directly from HTML, rather than using JavaScript. It extends HTML with attributes that enable AJAX requests, CSS transitions, WebSocket connections, and Server-Sent Events directly in your markup. The core philosophy is that HTML should be the primary medium for building web applications, returning to the original hypermedia-driven architecture of the web.

Created by Carson Gross, htmx is the successor to intercooler.js and weighs only about 14KB minified and gzipped. It works with any server-side language or framework since it simply exchanges HTML fragments over HTTP. This approach eliminates the need for complex JavaScript frameworks for many common web application patterns, reducing bundle sizes, simplifying architectures, and improving accessibility by default.

Installation

CDN

<!-- Latest version -->
<script src="https://unpkg.com/htmx.org@2.0.4"></script>

<!-- With integrity check -->
<script src="https://unpkg.com/htmx.org@2.0.4"
  integrity="sha384-HGfztofotfshcF7+8n44JQL2oJmowVChPTg48S+jvZoztPfvwD79OC/LTtG6dMp+"
  crossorigin="anonymous"></script>

npm

npm install htmx.org

# In your JavaScript entry
import 'htmx.org';

Minimal HTML Setup

<!DOCTYPE html>
<html>
<head>
  <script src="https://unpkg.com/htmx.org@2.0.4"></script>
</head>
<body>
  <button hx-get="/api/greeting" hx-target="#result">
    Click Me
  </button>
  <div id="result"></div>
</body>
</html>

Core Attributes

AJAX Requests

AttributeDescriptionExample
hx-getIssue GET requesthx-get="/api/users"
hx-postIssue POST requesthx-post="/api/users"
hx-putIssue PUT requesthx-put="/api/users/1"
hx-patchIssue PATCH requesthx-patch="/api/users/1"
hx-deleteIssue DELETE requesthx-delete="/api/users/1"

Targeting and Swapping

AttributeDescriptionExample
hx-targetElement to update with responsehx-target="#result"
hx-swapHow to swap contenthx-swap="innerHTML"
hx-selectSelect part of responsehx-select="#content"
hx-select-oobSelect out-of-band contenthx-select-oob="#sidebar"

Swap Strategies

StrategyDescription
innerHTMLReplace inner HTML (default)
outerHTMLReplace entire element
beforebeginInsert before element
afterbeginInsert at start of element
beforeendInsert at end of element
afterendInsert after element
deleteDelete target element
noneDon’t swap (just fire events)

Common Patterns

Click to Load

<button hx-get="/api/users"
        hx-target="#user-list"
        hx-swap="innerHTML"
        hx-indicator="#spinner">
  Load Users
</button>

<span id="spinner" class="htmx-indicator">Loading...</span>
<div id="user-list"></div>

Search with Debounce

<input type="search"
       name="q"
       hx-get="/api/search"
       hx-target="#results"
       hx-trigger="input changed delay:300ms, search"
       hx-indicator="#search-spinner"
       placeholder="Search...">

<div id="results"></div>

Infinite Scroll

<table>
  <tbody id="rows">
    <tr>
      <td>Row 1</td>
    </tr>
    <!-- Last row triggers loading more -->
    <tr hx-get="/api/rows?page=2"
        hx-target="#rows"
        hx-swap="beforeend"
        hx-trigger="revealed">
      <td>Row 10</td>
    </tr>
  </tbody>
</table>

Form Submission

<form hx-post="/api/contacts"
      hx-target="#contact-list"
      hx-swap="afterbegin"
      hx-on::after-request="this.reset()">
  <input name="name" required>
  <input name="email" type="email" required>
  <button type="submit">Add Contact</button>
</form>

<div id="contact-list"></div>

Inline Editing

<!-- Display mode -->
<div id="user-1" hx-target="this" hx-swap="outerHTML">
  <span>John Doe</span>
  <button hx-get="/api/users/1/edit">Edit</button>
</div>

<!-- Server returns edit form -->
<form id="user-1" hx-put="/api/users/1" hx-target="this" hx-swap="outerHTML">
  <input name="name" value="John Doe">
  <button type="submit">Save</button>
  <button hx-get="/api/users/1">Cancel</button>
</form>

Delete with Confirmation

<button hx-delete="/api/users/1"
        hx-target="closest tr"
        hx-swap="outerHTML swap:500ms"
        hx-confirm="Are you sure you want to delete this user?">
  Delete
</button>

Triggers

Trigger Modifiers

ModifierDescriptionExample
changedOnly if value changedhx-trigger="input changed"
delay:XsDebounce by X secondshx-trigger="input delay:500ms"
throttle:XsThrottle to every X secondshx-trigger="scroll throttle:200ms"
onceFire only oncehx-trigger="load once"
from:selectorListen on different elementhx-trigger="click from:body"
target:selectorFilter by event targethx-trigger="click target:.btn"
consumePrevent event propagationhx-trigger="click consume"
revealedWhen element scrolls into viewhx-trigger="revealed"
intersectIntersectionObserver triggerhx-trigger="intersect"
every XsPoll every X secondshx-trigger="every 5s"
loadOn element loadhx-trigger="load"

Examples

<!-- Multiple triggers -->
<input hx-get="/validate"
       hx-trigger="change, keyup delay:200ms changed">

<!-- Keyboard shortcut -->
<div hx-get="/refresh"
     hx-trigger="keyup[key=='r'] from:body">

<!-- Polling -->
<div hx-get="/api/notifications"
     hx-trigger="every 30s"
     hx-swap="innerHTML">
</div>

<!-- Load on page -->
<div hx-get="/api/stats"
     hx-trigger="load"
     hx-swap="innerHTML">
  Loading stats...
</div>

Additional Attributes

AttributeDescriptionExample
hx-includeInclude additional inputshx-include="[name='token']"
hx-valsAdd values to requesthx-vals='{"key": "value"}'
hx-headersAdd HTTP headershx-headers='{"X-Token": "abc"}'
hx-paramsFilter parametershx-params="*" or hx-params="not secret"
hx-indicatorShow during requesthx-indicator="#spinner"
hx-disabled-eltDisable during requesthx-disabled-elt="this"
hx-confirmConfirmation dialoghx-confirm="Are you sure?"
hx-push-urlPush URL to historyhx-push-url="true"
hx-boostProgressive enhancementhx-boost="true"
hx-preserveKeep element across swapshx-preserve="true"
hx-encodingRequest encodinghx-encoding="multipart/form-data"

Configuration

Meta Configuration

<meta name="htmx-config" content='{
  "defaultSwapStyle": "outerHTML",
  "defaultSwapDelay": 0,
  "defaultSettleDelay": 20,
  "includeIndicatorStyles": true,
  "historyCacheSize": 10,
  "useTemplateFragments": true,
  "scrollBehavior": "smooth",
  "getCacheBusterParam": false
}'>

CSS for Indicators

/* htmx adds/removes htmx-request class during requests */
.htmx-indicator {
  display: none;
}
.htmx-request .htmx-indicator {
  display: inline;
}
.htmx-request.htmx-indicator {
  display: inline;
}

/* Fade in swap */
.htmx-swapping {
  opacity: 0;
  transition: opacity 200ms ease-out;
}

Advanced Usage

Out-of-Band Swaps

<!-- Server response can update multiple elements -->
<!-- Main response updates target normally -->
<div id="message">Item created!</div>

<!-- OOB element updates sidebar regardless of target -->
<div id="sidebar" hx-swap-oob="true">
  Updated sidebar content
</div>

Server-Sent Events

<div hx-ext="sse"
     sse-connect="/api/events"
     sse-swap="message">
  Waiting for events...
</div>

<!-- Specific event types -->
<div hx-ext="sse" sse-connect="/api/events">
  <div sse-swap="notification"></div>
  <div sse-swap="update"></div>
</div>

WebSockets

<div hx-ext="ws" ws-connect="/ws/chat">
  <div id="messages"></div>
  <form ws-send>
    <input name="message">
    <button>Send</button>
  </form>
</div>

Response Headers

HeaderDescription
HX-RedirectClient-side redirect
HX-RefreshFull page refresh
HX-RetargetChange target element
HX-ReswapChange swap strategy
HX-TriggerTrigger client-side event
HX-Push-UrlPush URL to history
HX-Replace-UrlReplace URL in history
# Python/Flask example
@app.route("/api/item", methods=["POST"])
def create_item():
    # ... create item ...
    response = make_response(render_template("item.html", item=item))
    response.headers["HX-Trigger"] = "itemCreated"
    return response

JavaScript API

// Listen to htmx events
document.body.addEventListener("htmx:afterSwap", function(evt) {
  console.log("Swapped:", evt.detail.target);
});

document.body.addEventListener("htmx:beforeRequest", function(evt) {
  // Modify request
  evt.detail.xhr.setRequestHeader("X-Custom", "value");
});

// Programmatic requests
htmx.ajax("GET", "/api/data", { target: "#result", swap: "innerHTML" });

// Process new content
htmx.process(document.getElementById("new-content"));

Troubleshooting

ProblemSolution
Request not firingCheck hx-trigger; default is click for buttons, change for inputs
Wrong element updatingVerify hx-target selector; use browser DevTools
Content not swappingCheck server returns HTML fragments, not full pages
CSRF token issuesUse hx-headers to include token, or hx-vals
Double requestsCheck for duplicate triggers; use once modifier
History not workingAdd hx-push-url="true" to navigation requests
Events not bubblingUse from:body modifier on trigger
Extension not loadingEnsure extension script loaded after htmx; check hx-ext