Post

htmx: The Modern Way to Build Dynamic Websites Without Complex JavaScript

A practical guide to htmx, the library that lets you build modern, dynamic user interfaces directly in HTML, reducing your reliance on complex JavaScript frameworks.

htmx: The Modern Way to Build Dynamic Websites Without Complex JavaScript

Here’s a hands-on guide to htmx, a library that is changing how developers approach dynamic web interfaces by embracing the simplicity and power of HTML.

Introduction to htmx

What is htmx?

htmx is a dependency-free JavaScript library that allows you to make any HTML element issue an AJAX request. It extends HTML with a set of special attributes that tell htmx what event should trigger a request, what URL to hit, and how to place the returned content on the page.

The core idea is Hypermedia as the Engine of Application State (HATEOAS). Instead of fetching JSON from an API and rendering it with client-side JavaScript, your server sends back ready-to-render HTML fragments.

Why Choose htmx?

  • Simplicity: Write dynamic interfaces using familiar HTML attributes. No complex build steps, state management, or virtual DOM.
  • Reduced JavaScript: Drastically cut down on the amount of custom JavaScript you need to write.
  • Backend-Friendly: It pairs perfectly with any backend language or framework (Python/Django, Ruby/Rails, PHP/Laravel, etc.) that can render HTML.
  • Progressive Enhancement: Your site can work without JavaScript, and htmx enhances it with dynamic features.
  • Small Footprint: The library is lightweight and has no dependencies.

Key Attributes

The magic of htmx comes from a few core HTML attributes:

  • hx-get, hx-post, etc.: The HTTP method to use for the AJAX request.
  • hx-trigger: The event that triggers the request (e.g., click, keyup, load).
  • hx-target: A CSS selector that specifies where the response HTML should be placed.
  • hx-swap: Controls how the response HTML is swapped into the target element (e.g., innerHTML, outerHTML).

Setting Up htmx

Installation

The easiest way to get started is by using the CDN. Just add this script tag to the <head> of your HTML document.

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

That’s it! htmx is now active on your page.

Creating a Simple htmx Project

Create a new directory and two files: index.html and content.html.

index.html:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>htmx Demo</title>
    <script src="https://unpkg.com/[email protected]" defer></script>
</head>
<body>
    <h1>htmx "Hello World"</h1>
    <button hx-get="/content.html" hx-target="#response-div" hx-swap="innerHTML">
        Load Content
    </button>
    <div id="response-div"></div>
</body>
</html>

content.html:

1
<p><strong>Hello, htmx!</strong> This content was loaded dynamically.</p>

Running the “Hello World” App

You’ll need a simple local server to serve these files, as AJAX requests to local files (file://) are often blocked by browser security policies. You can use Python’s built-in server:

1
2
3
# In your project directory, run one of these commands:
python -m http.server 8000 # For Python 3
# python -m SimpleHTTPServer 8000 # For Python 2

Now, visit http://localhost:8000 in your browser. When you click the “Load Content” button, you’ll see the message from content.html appear in the <div> without a full page reload.

Core Concepts in Action

Let’s explore the key attributes with examples.

Triggers: hx-trigger

By default, the trigger is a click for buttons and an input or change for inputs. You can customize this.

Trigger on mouseover:

1
2
3
<div hx-get="/hover-content.html" hx-trigger="mouseover">
  Hover over me!
</div>

Trigger every 2 seconds:

1
2
3
<div hx-get="/server-time.html" hx-trigger="every 2s">
  Loading server time...
</div>

Targeting: hx-target

The hx-target attribute tells htmx where to put the HTML response.

1
2
3
4
5
6
7
<!-- The response from hx-get will replace the content of the #results div -->
<input type="search" name="q" 
       hx-get="/search" 
       hx-trigger="keyup changed delay:500ms" 
       hx-target="#results">

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

In this example, as you type in the search box, htmx will make a request to /search and place the results inside the <div> with the ID results.

Swapping: hx-swap

The hx-swap attribute controls how the new content is inserted relative to the target element.

  • innerHTML (default): Puts the content inside the target element.
  • outerHTML: Replaces the entire target element with the response.
  • beforeend: Appends the content as the last child of the target.
  • afterbegin: Prepends the content as the first child of the target.

Example: Infinite Scroll / Load More

1
2
3
4
5
6
7
8
9
10
11
12
<!-- index.html -->
<div id="items-list">
    <div>Item 1</div>
    <div>Item 2</div>
</div>
<button hx-get="/page2.html" hx-target="#items-list" hx-swap="beforeend">
    Load More
</button>

<!-- page2.html -->
<div>Item 3</div>
<div>Item 4</div>

When the button is clicked, the content of page2.html is appended to the #items-list div.

Working with Forms

htmx shines when handling form submissions. You can validate data and return errors without a page refresh.

1
2
3
4
5
6
7
8
<!-- HTML Form -->
<form hx-post="/register" hx-target="#form-response">
    <label>Email</label>
    <input type="email" name="email">
    <button type="submit">Register</button>
</form>

<div id="form-response"></div>

When the form is submitted, htmx sends a POST request to /register. Your backend can then process the data.

  • On Success, your server could return:
    1
    
    <p class="success">Thank you for registering!</p>
    
  • On Validation Error, your server could return:
    1
    
    <p class="error">Please enter a valid email address.</p>
    

In both cases, the response is placed in the #form-response div, providing instant feedback to the user.

Advanced Features (A Quick Look)

htmx offers more for complex scenarios:

  • Indicators: Show a loading state while a request is in flight. Simply add a class to an element that you want to show during the request.
    1
    2
    3
    4
    5
    
    <button hx-post="/save">
      Save
      <!-- This image is shown during the request -->
      <img src="/loader.gif" class="htmx-indicator">
    </button>
    
  • CSS Transitions: Smoothly transition new content into the DOM.
  • Boosting: The hx-boost="true" attribute on a parent element (like <body>) can convert all standard links and form submissions into AJAX requests automatically.
  • WebSockets & SSE: htmx has extensions for real-time communication with WebSockets and Server-Sent Events.

When to Use htmx?

htmx is an excellent choice for:

  • Server-Side Rendered (SSR) applications built with frameworks like Django, Rails, Laravel, or Express.
  • Content-heavy sites where interactivity is needed for specific components (e.g., forms, search, live updates).
  • Projects where you want to avoid a heavy, complex frontend toolchain.
  • Developers who prefer to keep their logic on the backend.

It might not be the best fit for highly complex, state-intensive client-side applications like Figma or Google Docs, where a framework like React or Vue might be more appropriate.

Conclusion

htmx offers a refreshing alternative to modern JavaScript-heavy frontend development. By extending HTML’s capabilities, it allows you to build rich, dynamic user experiences with surprising simplicity and elegance. It empowers backend developers to create modern UIs without leaving the comfort of their preferred stack. Give it a try on your next project—you might be surprised how much you can accomplish with just a few HTML attributes.

This post is licensed under CC BY 4.0 by the author.