Skip to content

HTMX for MPAs — Adding Dynamic Behavior with HTML Attributes

DodaTech Updated 2026-06-28 5 min read

In this tutorial, you will learn about HTMX for MPAs. We cover key concepts, practical examples, and best practices to help you master this topic.

HTMX extends MPAs with dynamic behavior using HTML attributes instead of JavaScript, enabling AJAX requests, WebSockets, CSS transitions, and partial page updates directly from server-rendered HTML.

What You'll Learn

By the end of this tutorial, you will understand what HTMX is, how it enables dynamic behavior through HTML attributes, how to make AJAX requests with hx-get, hx-post, and hx-trigger, how to update page content with hx-target and hx-swap, and how to build interactive MPAs without writing client-side JavaScript.

Why It Matters

HTMX challenges the assumption that dynamic web applications require a JavaScript framework. With HTMX, you build interactive applications using server-rendered HTML and a small JavaScript library. This simplifies development, reduces bundle size, and keeps your logic on the server where it belongs.

Real-World Use

A team replaced their React SPA with an HTMX-powered MPA and reduced their codebase from 50,000 lines of JavaScript to 5,000 lines of server-side templates. Page load time dropped 60 percent, development velocity increased, and the application remained just as interactive.

HTMX Request Flow
    ┌──────────┐             ┌──────────┐
    │ Browser  │             │  Server  │
    └────┬─────┘             └────┬─────┘
         │                        │
         │  No HTMX:              │
         │  Full page load        │
         │  for every interaction │
         │                        │
         │  With HTMX:            │
         │                        │
         │  <button hx-get=       │
         │   "/api/data"          │
         │   hx-target="#result"> │
         │   Load Data            │
         │  </button>             │
         │                        │
         │  ─────────────────────>│
         │                        │
         │  HTML response         │
         │  <─────────────────────│
         │                        │
         │  Swaps into #result    │
         │  No page reload        │
         │                        │
         │  Other HTMX triggers:  │
         │  hx-trigger="click"    │
         │  hx-trigger="submit"   │
         │  hx-trigger="revealed" │
         │  hx-trigger="every 30s"│
         └────────────────────────┘

Think of HTMX like smart light switches. Normal HTML links are manual switches — you physically walk to the switch and flip it (full page load). HTMX attributes are motion-activated lights — sensors detect movement (user action) and the light turns on (content updates) without you walking anywhere. The wiring (server logic) stays in the same place.

Basic HTMX Examples

<!-- Include HTMX from CDN -->
<script src="https://cdn.jsdelivr.net/npm/htmx.org@1/dist/htmx.min.js"
        defer></script>

<!-- Basic AJAX request on click -->
<button hx-get="/api/time"
        hx-target="#current-time"
        hx-swap="innerHTML">
    Refresh Time
</button>
<div id="current-time">
    <!-- Server response replaces this content -->
    Loading...
</div>

<!-- Server returns HTML fragment -->
<!--
    <p>The current time is 2:45 PM UTC</p>
-->

<!-- Form submission with HTMX -->
<form hx-post="/api/search"
      hx-target="#search-results"
      hx-trigger="submit"
      hx-swap="innerHTML"
      hx-indicator="#search-spinner">

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

    <button type="submit">Search</button>
    <span id="search-spinner" class="htmx-indicator">
        Searching...
    </span>
</form>
<ul id="search-results"></ul>
<div id="suggestions"></div>

Advanced HTMX Patterns

<!-- Infinite scroll with hx-trigger="revealed" -->
<div hx-get="/api/posts?page=1"
     hx-trigger="load"
     hx-target="#post-list"
     hx-swap="innerHTML">
    Loading posts...
</div>
<div id="post-list"></div>

<div hx-get="/api/posts?page=2"
     hx-trigger="revealed"
     hx-target="#post-list"
     hx-swap="beforeend">
</div>

<!-- Lazy load with polling -->
<div hx-get="/api/job-status/123"
     hx-trigger="every 2s"
     hx-target="#job-status"
     hx-swap="innerHTML">
    Checking job status...
</div>
<div id="job-status"></div>

<!-- When job completes, server returns:
<div>
    Job complete! <a href="/download/123">Download Results</a>
    <script>clearInterval(htmx.find('#job-status'))</script>
</div>
-->

<!-- Delete with confirmation and optimistic UI -->
<button hx-delete="/api/items/42"
        hx-target="#item-42"
        hx-swap="outerHTML"
        hx-confirm="Are you sure you want to delete this item?"
        hx-on:htmx:before-request="this.textContent='Deleting...'">
    Delete
</button>

HTMX with Server-Side Validation

<!-- Form with inline validation -->
<form hx-post="/api/register"
      hx-target="#form-errors"
      hx-swap="innerHTML">

    <label>Email</label>
    <input type="email"
           name="email"
           hx-post="/api/validate/email"
           hx-target="#email-error"
           hx-trigger="change delay:500ms">
    <div id="email-error"></div>

    <label>Password</label>
    <input type="password"
           name="password"
           hx-post="/api/validate/password"
           hx-target="#password-error"
           hx-trigger="change delay:500ms">
    <div id="password-error"></div>

    <button type="submit">Register</button>
</form>
<div id="form-errors"></div>

<!-- Server validation response:
<div class="error">Email already registered</div>
-->

<!-- Server success response with redirect:
<div hx-redirect="/welcome">Registration successful!</div>
-->

Common Mistakes

  1. Returning JSON instead of HTML. HTMX expects HTML responses, not JSON. Always return HTML fragments from the server. The response is inserted directly into the DOM.
  2. Not using hx-target. Without hx-target, the response replaces the triggering element. Usually you want to update a different element on the page.
  3. Overusing HTMX for complex state. HTMX excels at interactions. For complex client-side state (drag-and-drop, real-time collaborative editing), a JavaScript framework may be more appropriate.
  4. Forgetting CSRF protection. HTMX requests need CSRF tokens. Include the token in headers using hx-headers or meta tags.
  5. Not handling errors. Use hx-on:htmx:responseError to handle error responses and show user-friendly messages. The server should return appropriate HTTP status codes.

Practice Questions

  1. How does HTMX enable dynamic behavior without writing JavaScript?
  2. What is the difference between hx-get, hx-post, and hx-delete?
  3. How does hx-target and hx-swap control where content is placed?
  4. What triggers are available for HTMX requests?
  5. Why does HTMX expect HTML responses instead of JSON?

Challenge: Build a task management MPA using HTMX: task list with hx-get for loading, inline add form with hx-post (returns the new task HTML), delete button with hx-delete and confirmation, status toggle with hx-put that updates the checkbox, polling for real-time updates every 10 seconds, and inline validation for the task name field.

FAQ

Do I need to know JavaScript to use HTMX?

No. HTMX is designed to let you build interactive applications with HTML and server-side code. For advanced use cases, you may want some JavaScript, but it is not required.

Is HTMX a replacement for React?

Not exactly. HTMX replaces React for applications that benefit from server-rendered HTML with dynamic updates. For highly interactive client-side applications, React may still be better.

How does HTMX handle large responses?

HTMX swaps HTML fragments efficiently. For large responses, use hx-swap with appropriate strategies (innerHTML, outerHTML, beforeend, afterbegin).

Can I use HTMX with WebSockets?

Yes. HTMX supports WebSockets with hx-ws attribute. Server push updates to the client without polling.

Does HTMX work with all server-side frameworks?

Yes. HTMX works with any framework that returns HTML, including Express, Django, Rails, Laravel, Spring, and PHP.

Mini Project

Build a real-time dashboard MPA with HTMX: server-side rendered widgets with auto-refresh every 30 seconds using hx-trigger="every 30s", interactive forms for updating settings with hx-put, search with live suggestions using hx-trigger="keyup changed delay:300ms", delete items with confirmation dialog, and a status bar that polls a job queue. No custom JavaScript required.

What's Next

You understand HTMX for MPAs. Now learn about Progressive Enhancement to build MPAs that work without JavaScript.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro