SuperAPI AI Directory

htmx

Frontend & websites

View repository

Updated 10-16 16:58

About this skill

htmx

High power tools for HTML. Allows you to build modern user interfaces with the simplicity of hypertext.

When to Use

  • Hypermedia-Driven Web Architectures: Building dynamic user interfaces using server-rendered HTML over the wire without heavy SPA frameworks.
  • CRUD Backends with Existing Templating: Django, Rails, Laravel, Go templates, or Spring Boot apps wanting SPA-like responsiveness.
  • Instant Search, Inline Editing, and Polling: Enhancing HTML forms and tables with declarative AJAX attributes.
  • Low-JavaScript Minimal Bundles: Drastically reducing client bundle sizes and eliminating frontend state management complexity.

Quick Start

<script src="https://unpkg.com/htmx.org@1.9.10"></script>

<!-- When clicked, issue GET to /clicked, swap outerHTML with response -->
<button hx-get="/clicked" hx-swap="outerHTML">Click Me</button>

Core Concepts

Declarative AJAX with Trigger & Swap Directives

Making async requests and swapping server HTML directly into DOM targets:

<!-- Instant search input with debouncing -->
<div class="search-widget">
  <input
    type="text"
    name="q"
    placeholder="Search users..."
    hx-post="/api/users/search"
    hx-trigger="keyup changed delay:300ms, search"
    hx-target="#search-results"
    hx-indicator="#loading-spinner"
    class="form-control"
  />
  <div id="loading-spinner" class="htmx-indicator">Searching...</div>
  <div id="search-results">
    <!-- Server returns <ul> list HTML here -->
  </div>
</div>

Inline Editing with Target Swapping

Updating tabular records in-place without page reloads:

<!-- Table Row Component -->
<tr id="user-row-42">
  <td>Alice Johnson</td>
  <td>alice@example.com</td>
  <td>
    <button
      class="btn btn-sm btn-primary"
      hx-get="/users/42/edit"
      hx-target="#user-row-42"
      hx-swap="outerHTML"
    >
      Edit
    </button>
  </td>
</tr>

Out-of-Band (OOB) Updates & Server-Sent Events

Updating multiple disconnected DOM regions from a single response:

<!-- Server response contains main payload and an OOB notification badge -->
<div id="order-details">
  <h4>Order #1029 Confirmed</h4>
  <p>Status: Processing</p>
</div>

<!-- Out-of-band swap updates the navbar cart badge automatically -->
<span id="cart-counter" hx-swap-oob="true" class="badge bg-danger"> 3 </span>

Common Patterns

Inline Search with Debounced Active Search

Problem: Building responsive autocomplete search typically requires heavy React/Vue state machines.

Solution: Use declarative htmx attributes on standard HTML input:

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

<div id="loading-spinner" class="htmx-indicator">Searching...</div>
<div id="search-results"></div>

Best Practices

Do:

  • Return clean HTML partials/fragments from the server instead of entire HTML document trees for htmx endpoints.
  • Use hx-indicator to provide visual loading indicators and spinners for every network interaction.
  • Leverage hx-boost="true" on root layout links and forms for instant progressive enhancement.
  • Validate and sanitize all server-rendered HTML to neutralize Cross-Site Scripting (XSS).

Don't:

  • Return JSON from htmx endpoints; htmx is fundamentally designed for server-rendered HTML.
  • Neglect accessibility; announce dynamic swaps to screen readers using aria-live="polite".
  • Overuse client-side scripting when declarative htmx attributes can achieve the desired interaction.

Troubleshooting

Error Cause Solution
htmx:targetError: Target element does not exist Element specified in hx-target ID selector is missing from DOM. Verify ID in DOM: #search-results must exist before request triggers.
Response received but nothing swapped Server returned empty response or non-200 HTTP code. Verify server returns HTML with HTTP 200, or configure hx-swap response codes.
Infinite request loop hx-trigger="load" placed inside swapped HTML fragment. Move load trigger to outer persistent container or remove trigger on swap.

References