DevToolBoxGRATIS
Blogg

HTMX Guide 2026: Webapper Uten JavaScript-rammeverk

13 minby DevToolBox

HTMX is a lightweight library that allows you to access modern browser features directly from HTML, without writing JavaScript. By extending HTML with attributes like hx-get, hx-post, and hx-swap, HTMX enables dynamic, interactive web applications using a hypermedia-driven approach. In 2026, HTMX has become a serious alternative to React, Vue, and Angular for many use cases.

What Is HTMX?

HTMX is a dependency-free JavaScript library (about 14KB minified and gzipped) that extends HTML with custom attributes for making AJAX requests, handling server-sent events, WebSocket connections, CSS transitions, and more. Instead of sending JSON and rendering on the client, HTMX sends HTML fragments from the server and swaps them into the DOM.

<!-- Add HTMX to your page -->
<script src="https://unpkg.com/htmx.org@2.0.0"></script>

<!-- A simple HTMX example: click to load content -->
<button hx-get="/api/greeting" hx-target="#result" hx-swap="innerHTML">
  Say Hello
</button>
<div id="result"></div>

<!-- The server returns HTML, not JSON -->
<!-- GET /api/greeting response: -->
<!-- <p>Hello, World! The time is 2026-02-23 10:30:00</p> -->

Core Concepts

HTMX follows the hypermedia-as-the-engine-of-application-state (HATEOAS) principle. The server sends HTML, and the browser renders it. This simplifies the architecture by eliminating the need for a separate API layer, client-side state management, and complex build tooling.

Core Attributes

HTMX uses HTML attributes to declare behavior. The core attributes control what HTTP request to make and how to update the page.

<!-- HTTP method attributes -->
<button hx-get="/api/items">Load Items</button>
<form hx-post="/api/items">...</form>
<button hx-put="/api/items/1">Update</button>
<button hx-patch="/api/items/1">Partial Update</button>
<button hx-delete="/api/items/1">Delete</button>

<!-- Include values with requests -->
<input type="text" name="search" hx-get="/api/search"
       hx-trigger="keyup changed delay:300ms"
       hx-target="#search-results" />

<!-- Send additional values -->
<button hx-post="/api/vote"
        hx-vals='{"itemId": 42, "direction": "up"}'>
  Upvote
</button>

Content Swapping

The hx-swap attribute controls how the response HTML replaces content in the DOM. Options include innerHTML (default), outerHTML, beforebegin, afterbegin, beforeend, afterend, delete, and none.

<!-- innerHTML: replace children (default) -->
<div hx-get="/items" hx-swap="innerHTML">Loading...</div>

<!-- outerHTML: replace the entire element -->
<div hx-get="/card" hx-swap="outerHTML">Old card</div>

<!-- beforeend: append to children -->
<ul hx-get="/more-items" hx-swap="beforeend">
  <li>Item 1</li>
</ul>

<!-- afterend: insert after the element -->
<tr hx-get="/next-row" hx-swap="afterend">...</tr>

<!-- Swap with transition timing -->
<div hx-get="/content" hx-swap="innerHTML swap:300ms settle:100ms">
  Content with transition
</div>

Event Triggers

The hx-trigger attribute controls when a request is sent. By default, buttons use click, inputs use change, and forms use submit. You can customize triggers with modifiers like delay, throttle, changed, and once.

<!-- Trigger on keyup with 300ms debounce -->
<input hx-get="/search" hx-trigger="keyup changed delay:300ms" />

<!-- Trigger once when element becomes visible -->
<div hx-get="/lazy-content" hx-trigger="revealed">Loading...</div>

<!-- Trigger every 5 seconds (polling) -->
<div hx-get="/live-data" hx-trigger="every 5s">Current data</div>

<!-- Trigger on intersection observer -->
<div hx-get="/more" hx-trigger="intersect threshold:0.5">
  Load when 50% visible
</div>

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

Common Patterns

HTMX supports many interactive patterns that traditionally require significant JavaScript code.

Active Search

Implement a search-as-you-type feature with a single attribute. The server returns matching results as HTML, and HTMX swaps them into the results container.

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

<span id="search-spinner" class="htmx-indicator">
  Searching...
</span>

<table>
  <thead><tr><th>Name</th><th>Email</th></tr></thead>
  <tbody id="search-results">
    <!-- Server returns <tr> elements -->
  </tbody>
</table>

Infinite Scroll

Load more content as the user scrolls down. The revealed trigger fires when an element enters the viewport.

<!-- Infinite scroll -->
<div id="feed">
  <div class="post">Post 1</div>
  <div class="post">Post 2</div>
  <!-- Last element triggers loading more -->
  <div hx-get="/api/posts?page=2"
       hx-trigger="revealed"
       hx-swap="afterend"
       hx-select="div.post">
    Loading more...
  </div>
</div>

Click-to-Edit

Replace static content with an edit form on click, then swap back to the updated static content on save.

<!-- Click to edit pattern -->
<div hx-get="/contacts/1/edit" hx-trigger="click" hx-swap="outerHTML">
  <p>Name: Alice Johnson</p>
  <p>Email: alice@example.com</p>
  <small>Click to edit</small>
</div>

<!-- Server returns edit form: -->
<!--
<form hx-put="/contacts/1" hx-swap="outerHTML">
  <input name="name" value="Alice Johnson" />
  <input name="email" value="alice@example.com" />
  <button type="submit">Save</button>
  <button hx-get="/contacts/1" hx-swap="outerHTML">Cancel</button>
</form>
-->

Server-Side Integration

HTMX works with any server-side language or framework. The server returns HTML fragments instead of JSON. Here are examples with popular backend frameworks.

Express.js (Node.js)

// Express.js server for HTMX
const express = require("express");
const app = express();

app.get("/api/search", (req, res) => {
  const query = req.query.q || "";
  const results = searchUsers(query);

  // Return HTML fragment, not JSON
  const html = results.map(user =>
    `<tr>
      <td>${user.name}</td>
      <td>${user.email}</td>
      <td>
        <button hx-delete="/api/users/${user.id}"
                hx-target="closest tr"
                hx-swap="outerHTML swap:500ms"
                hx-confirm="Delete ${user.name}?">
          Delete
        </button>
      </td>
    </tr>`
  ).join("");

  res.send(html);
});

Flask (Python)

# Flask server for HTMX
from flask import Flask, request, render_template_string

app = Flask(__name__)

@app.route("/api/search")
def search():
    query = request.args.get("q", "")
    results = search_users(query)

    return render_template_string("""
    {% for user in results %}
    <tr>
      <td>{{ user.name }}</td>
      <td>{{ user.email }}</td>
    </tr>
    {% endfor %}
    """, results=results)

Advanced Features

Out-of-Band Swaps

Update multiple parts of the page from a single response using hx-swap-oob. Elements with this attribute are swapped into matching elements by ID, regardless of the main target.

<!-- Out-of-band swaps: update multiple elements -->
<!-- Main response (swaps into target): -->
<div id="main-content">
  <p>Item saved successfully!</p>
</div>

<!-- Additional out-of-band updates: -->
<span id="notification-count" hx-swap-oob="true">3</span>
<div id="sidebar-stats" hx-swap-oob="innerHTML">
  <p>Total items: 42</p>
</div>
<tr id="item-row-5" hx-swap-oob="outerHTML">
  <td>Updated Item</td>
  <td>2026-02-23</td>
</tr>

CSS Transitions

HTMX adds CSS classes during swaps that you can use for animations. The htmx-added, htmx-settling, and htmx-swapping classes enable smooth transitions.

/* CSS transitions with HTMX */
.fade-in {
  opacity: 0;
  transition: opacity 300ms ease-in;
}
.fade-in.htmx-added {
  opacity: 0;
}
.fade-in.htmx-settling {
  opacity: 1;
}

/* Swapping out animation */
.htmx-swapping {
  opacity: 0;
  transition: opacity 200ms ease-out;
}

/* Loading indicator */
.htmx-indicator {
  display: none;
}
.htmx-request .htmx-indicator {
  display: inline-block;
}

HTMX vs JavaScript Frameworks

CriteriaHTMXReact/Vue/Angular
Bundle size~14KB44KB+ (React + ReactDOM)
Learning curveLow (HTML attributes)High (JSX, hooks, state)
Build toolingNone requiredWebpack/Vite/etc required
TypeScriptNot neededStrongly recommended
State managementServer-sideClient-side (Redux, Zustand)
SEOExcellent (server HTML)Requires SSR/SSG
Offline supportLimitedService workers + PWA
Complex UIModerateExcellent
Server couplingTightLoose (API-driven)

Best Practices

  • Return small HTML fragments, not full pages, for partial updates
  • Use hx-indicator to show loading states during requests
  • Leverage hx-push-url for proper browser history support
  • Use hx-boost on links and forms for progressive enhancement
  • Keep server responses idempotent where possible
  • Use hx-swap-oob for updating multiple page regions from one response
  • Implement proper error handling with hx-on::after-request
  • Use the preload extension for anticipated navigation

Frequently Asked Questions

Can HTMX replace React?

For many applications, yes. HTMX excels at server-rendered applications with moderate interactivity: dashboards, admin panels, content sites, CRUD applications, and forms. For highly interactive client-side applications (real-time collaborative editing, complex data visualizations, offline-first apps), a JavaScript framework is still better suited.

Is HTMX SEO-friendly?

Yes, extremely. Since HTMX serves HTML from the server, search engines can crawl and index your content without executing JavaScript. This is a major advantage over client-side rendered SPAs. You get server-side rendering by default.

Does HTMX work with REST APIs?

HTMX is designed for hypermedia APIs that return HTML, not JSON. You can use it with existing REST APIs by adding a server-side layer that renders HTML templates. Alternatively, the json-enc extension lets HTMX send JSON requests.

What is the performance like?

HTMX is extremely lightweight (14KB) compared to React (44KB+) or Vue (34KB+). Page loads are fast because there is no client-side rendering step. Network requests transfer small HTML fragments instead of JSON that needs to be parsed and rendered. The trade-off is that the server does more rendering work.

Can HTMX handle complex state management?

HTMX moves state management to the server, which simplifies the client but requires your server to maintain state across requests. For complex client-side state (undo/redo, drag-and-drop, real-time collaboration), you may need to combine HTMX with targeted JavaScript using Alpine.js or vanilla JS.

𝕏 Twitterin LinkedIn
Var dette nyttig?

Hold deg oppdatert

Få ukentlige dev-tips og nye verktøy.

Ingen spam. Avslutt når som helst.

Try These Related Tools

JSXHTML to JSX{ }JSON Formatter

Related Articles

Astro vs Next.js 2026: Islands-arkitektur vs RSC

Grundig sammenligning Astro og Next.js 2026: islands, RSC, ytelse og SEO.

Nettytelsesoptimalisering: Core Web Vitals Guide 2026

Komplett guide til nettytelsesoptimalisering og Core Web Vitals. Forbedre LCP, INP og CLS med praktiske teknikker for bilder, JavaScript, CSS og caching.