HTMX tutorial 2026: build interactive UI without a JS framework

# tutorials · htmx

HTMX tutorial 2026: build interactive UI without a JS framework.

HTMX lets you build interactive UI with attributes on plain HTML: `hx-get`, `hx-post`, `hx-swap`, `hx-target`. No React, no build step, no client-side router. This tutorial walks through the six attributes you will use daily, then wires HTMX to a real WordPress REST endpoint for a live-search demo.

The premise

HTMX turns the browser into a smarter HTML client. Instead of shipping JSON to a React app that renders HTML, you ship HTML from the server and let HTMX swap fragments into the DOM. The result: less code, faster page loads, no hydration cost.

HTMX is not a React replacement for a Figma-plugin dashboard. It is a React replacement for the 80% of web UI that is a form, a table, a modal, or a live search box.

Install: one script tag

<script src="https://unpkg.com/[email protected]" crossorigin="anonymous"></script>

That is the whole install. Add it to the <head> of any HTML page (WordPress theme, static site, Rails app, WordPress block editor) and every element with an hx-* attribute becomes reactive.

Attribute 1: hx-get (load HTML on click)

<button hx-get="/fragments/latest-posts.html" hx-target="#posts">
  Load latest posts
</button>
<div id="posts"></div>

On click, HTMX fires a GET to /fragments/latest-posts.html and swaps the response HTML into #posts. The server returns rendered HTML (not JSON), which is exactly what a WordPress action endpoint or a Rails partial already does.

Attribute 2: hx-post (submit a form)

<form hx-post="/api/subscribe" hx-target="#result" hx-swap="innerHTML">
  <input type="email" name="email" required>
  <button type="submit">Subscribe</button>
</form>
<div id="result"></div>

Form submits via AJAX to /api/subscribe. Server responds with an HTML snippet (a success message, or a form with errors). HTMX swaps it into #result. No JavaScript required on the client.

Attribute 3: hx-swap (control where HTML lands)

<!-- Replace the target inner HTML (default). -->
<div hx-get="/tick" hx-swap="innerHTML">Loading</div>

<!-- Replace the target element entirely. -->
<div hx-get="/tick" hx-swap="outerHTML">Loading</div>

<!-- Insert before/after target. -->
<div hx-get="/new-row" hx-swap="beforeend" hx-target="#table tbody">Add row</div>

The hx-swap options are innerHTML (default), outerHTML, beforebegin, afterbegin, beforeend, afterend, and delete. Match the DOM insertion behaviour you want.

Attribute 4: hx-trigger (fire on something other than click)

<!-- Live search: fire on keyup after 300ms of inactivity. -->
<input type="search" name="q"
       hx-get="/search"
       hx-trigger="keyup changed delay:300ms"
       hx-target="#results">
<div id="results"></div>

<!-- Poll every 5 seconds. -->
<div hx-get="/status" hx-trigger="every 5s">Loading status</div>

<!-- Fire when element enters viewport. -->
<div hx-get="/lazy" hx-trigger="revealed">Loading below the fold</div>

hx-trigger unlocks live search, polling, lazy load below the fold, and infinite scroll (hx-trigger="revealed" hx-swap="afterend"). Modifiers like changed and delay:300ms match what you would write in a React useEffect, without the effect.

Attribute 5: hx-target (send response somewhere else)

<!-- Click the button, response lands in #modal. -->
<button hx-get="/user/42/edit" hx-target="#modal" hx-swap="innerHTML">
  Edit user
</button>
<div id="modal"></div>

<!-- CSS selectors work: closest, next, previous. -->
<tr>
  <td>Row 1</td>
  <td><button hx-delete="/row/1" hx-target="closest tr" hx-swap="outerHTML">
    Delete row
  </button></td>
</tr>

closest, next, previous, and find are all valid target selectors. Combined with hx-swap="outerHTML", you get row-delete behaviour with three attributes and zero JavaScript.

Wire HTMX to a WordPress REST endpoint

The WordPress REST API returns JSON by default, but HTMX wants HTML. Two options: (1) register an endpoint that returns HTML, or (2) let HTMX call admin-ajax.php. Option 1 is cleaner.

<?php
// functions.php
add_action('rest_api_init', function() {
    register_rest_route('wdb/v1', '/search', [
        'methods'  => 'GET',
        'callback' => 'wdb_search_html',
        'permission_callback' => '__return_true',
    ]);
});

function wdb_search_html($req) {
    $q = sanitize_text_field($req->get_param('q'));
    $posts = get_posts([
        'post_type'   => 'post',
        's'           => $q,
        'numberposts' => 5,
    ]);
    ob_start();
    if (empty($posts)) {
        echo '<p>No results.</p>';
    } else {
        echo '<ul>';
        foreach ($posts as $p) {
            printf('<li><a href="%s">%s</a></li>',
                esc_url(get_permalink($p)),
                esc_html($p->post_title)
            );
        }
        echo '</ul>';
    }
    $html = ob_get_clean();
    $resp = new WP_REST_Response($html);
    $resp->header('Content-Type', 'text/html');
    return $resp;
}

Then on the frontend:

<input type="search"
       name="q"
       hx-get="/wp-json/wdb/v1/search"
       hx-trigger="keyup changed delay:300ms"
       hx-target="#results"
       placeholder="Search posts">
<div id="results"></div>

That is a live-search box with autocomplete, no build step, no React, no bundle. Total client-side JavaScript: 14KB of HTMX. Total custom JS: zero.

When HTMX is the right answer

  • WordPress site with a live search, filter, or newsletter modal.
  • Rails, Laravel, Django, or Phoenix app with mostly server-rendered HTML.
  • Admin dashboard where you already have a template engine.
  • Marketing site that needs a bit of interactivity (modal, tabs, live count).

When to reach for React or Vue instead

  • Complex client-side state (a Figma-style editor, a spreadsheet).
  • Offline-first PWA with heavy IndexedDB usage.
  • Real-time collaboration (multiplayer cursor, live document editing).
  • You already have a React app and adding HTMX would fragment the stack.

affiliate disclosure: Web Dev Blog carries affiliate links to Kinsta, WP Engine, Cloudways, DigitalOcean, and Vercel. If you sign up via a link on this site we may receive a commission at no cost to you. This does not influence editorial verdicts, which are based on real tests from a London server. Full policy on /affiliate-disclosure/.