TL;DR: HTMX is a tiny library that supercharges HTML with new attributes like hx-get, hx-post, and hx-trigger. Those attributes let any HTML element make server requests and update the page — without writing JavaScript. AI tools generate HTMX code reliably. It's simpler than React for most server-side apps, and the right choice when you want dynamic behavior without a full frontend framework. One script tag and you're in.

The Problem HTMX Solves

Picture this: you've built a product search page. It works. Users type a query, hit submit, the page reloads, results appear. Classic. Functional. Boring.

Now you want it to feel modern — search results that update as the user types, without a full page reload. The kind of thing you see on every major site. So you ask Claude to add that feature.

Claude comes back with a React component. Now you need to install Node. Set up a bundler. Learn about JSX. Figure out where your server fits in. What was a simple HTML page is now a whole project.

Or: Claude comes back with raw JavaScript. It's 80 lines of fetch calls, event listeners, and DOM manipulation. It works, mostly, but you have no idea how to modify it when the product manager asks for a slight change next week.

HTMX offers a third path. Instead of writing JavaScript, you add an attribute to your existing HTML element:

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

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

That's it. When the user types, HTMX waits 300 milliseconds, sends a GET request to /search with whatever they typed, and drops the server's HTML response into #results. No JavaScript written. No framework installed. No bundler. Three attributes on an input field.

That's the core of what HTMX does: it takes the things browsers have always been able to do — make HTTP requests, display HTML — and makes them available to any HTML element, triggered by any event. Not just forms submitting on click. Any element. Any event.

If you're still getting your head around what HTML is in the first place, check out our introduction to HTML first — HTMX builds directly on top of it.

How HTMX Actually Works (No Jargon)

To understand HTMX, you need to know one thing about how traditional web pages work: when your browser needs new content, it makes an HTTP request to a server, and the server sends back HTML. The browser displays it.

That's been true since 1991. The problem is that classic HTML only lets two elements trigger those requests: <a> tags (links, on click) and <form> tags (on submit). Everything else — buttons that load content, dropdowns that fetch options, infinite scroll — requires JavaScript.

HTMX removes that limitation. It intercepts the browser's normal behavior and extends it. Now any element can:

  • Send a GET, POST, PUT, PATCH, or DELETE request to your server
  • Be triggered by any browser event — click, keyup, mouseover, scroll, even a timer
  • Swap the response HTML into any part of the page

The key insight is what HTMX expects back from the server. React and most modern JavaScript approaches expect the server to return JSON — raw data — and then JavaScript transforms that data into HTML on the browser. HTMX expects the server to return HTML directly. Your server does the rendering. HTMX just drops the result where you told it to.

This is called server-side rendering, and it's how the web worked for its first 20 years. HTMX makes it interactive again — without abandoning it for a client-side framework.

The Four Attributes You'll Actually Use

HTMX has a lot of attributes, but 90% of what you'll do comes down to four:

hx-get / hx-post — Which HTTP method to use and where to send it. hx-get="/search" makes a GET request to /search. hx-post="/comments" makes a POST request to /comments. These are the same requests your forms have always made — HTMX just lets anything make them.

hx-trigger — What event fires the request. Defaults to click for buttons and links, change for inputs. But you can set it to anything: keyup, mouseover, revealed (when the element scrolls into view — great for infinite scroll), or even every 5s for a polling dashboard.

hx-target — Where to put the response. Uses CSS selectors. hx-target="#results" drops the HTML into the element with id results. hx-target="closest .card" finds the nearest parent with class card. Leave it out and HTMX replaces the element that made the request.

hx-swap — How to insert the response. innerHTML (default) replaces the contents. outerHTML replaces the whole element. beforeend appends to the end — perfect for infinite scroll. afterbegin prepends — great for new items appearing at the top of a list.

Real Example: Search-as-You-Type in 20 Lines

Let's build something real. A product search that updates as the user types — the kind of feature that used to require a JavaScript developer.

Here's the complete front-end HTML:

<!DOCTYPE html>
<html>
<head>
  <title>Product Search</title>
  <script src="https://unpkg.com/htmx.org@2.0.0"></script>
</head>
<body>

  <h1>Find a Product</h1>

  <input
    type="text"
    name="q"
    hx-get="/search"
    hx-trigger="keyup changed delay:300ms"
    hx-target="#results"
    hx-indicator="#spinner"
    placeholder="Start typing..."
    autofocus
  >

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

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

</body>
</html>

One script tag. One input. One div. That's the entire front end. Now the server side — here's a Python/Flask example, but the same logic works in any language:

from flask import Flask, request, render_template_string

app = Flask(__name__)

PRODUCTS = [
    "Circular Saw", "Cordless Drill", "Framing Hammer",
    "Tape Measure", "Level", "Speed Square", "Utility Knife",
    "Work Boots", "Safety Glasses", "Hearing Protection",
    "Caulk Gun", "Pry Bar", "Chalk Line", "Stud Finder",
]

RESULT_TEMPLATE = """
{% for product in products %}
  <div class="result-item">{{ product }}</div>
{% else %}
  <p>No products found.</p>
{% endfor %}
"""

@app.route("/search")
def search():
    query = request.args.get("q", "").lower()
    matches = [p for p in PRODUCTS if query in p.lower()]
    return render_template_string(RESULT_TEMPLATE, products=matches)

if __name__ == "__main__":
    app.run(debug=True)

Walk through what happens when a user types "drill":

  1. HTMX waits 300ms after the last keystroke (that's the delay:300ms part — prevents a request on every single character)
  2. HTMX sends GET /search?q=drill to your Flask server
  3. Flask filters the products list and returns a few <div> tags as HTML
  4. HTMX drops that HTML into #results
  5. The user sees results appear — no page reload

The spinner element with class htmx-indicator is automatically shown during the request and hidden when it completes. HTMX manages that for free.

No JavaScript written. No state to manage. No React component tree to reason about. The server does what servers have always done — filter data, render HTML, send it back.

Real Example: Infinite Scroll With One Attribute

Infinite scroll is one of those features that sounds complex but becomes almost trivial with HTMX. The trick is the revealed trigger — HTMX fires it when an element scrolls into the user's viewport.

<!-- Your list of items -->
<ul id="item-list">
  <li>Item 1</li>
  <li>Item 2</li>
  <li>Item 3</li>
  <!-- ... more items ... -->

  <!-- This "load more" sentinel triggers when it scrolls into view -->
  <li
    hx-get="/items?page=2"
    hx-trigger="revealed"
    hx-target="#item-list"
    hx-swap="beforeend"
    hx-indicator="#loading"
  >
    <span id="loading" class="htmx-indicator">Loading more...</span>
  </li>
</ul>

When that last <li> scrolls into view, HTMX fetches /items?page=2. The server returns the next page of items as HTML list items. HTMX appends them to #item-list using beforeend.

The key is that your server response should include a new sentinel element pointing to page 3, so the scroll chain continues:

<!-- Server response for page 2 -->
<li>Item 21</li>
<li>Item 22</li>
<li>Item 23</li>
<!-- ... -->
<li>Item 40</li>

<!-- New sentinel pointing to page 3 -->
<li
  hx-get="/items?page=3"
  hx-trigger="revealed"
  hx-target="#item-list"
  hx-swap="beforeend"
>
  <span class="htmx-indicator">Loading more...</span>
</li>

Each page response replaces the old sentinel with new items and a new sentinel. The scroll continues naturally until the server stops including a sentinel (because there are no more pages).

That's infinite scroll. No JavaScript. No intersection observer API. No state tracking page numbers in the browser. The server tracks state. The browser just displays what it receives.

Prompt to use with your AI:

"I have a Python Flask app with a route that returns a list of blog posts as JSON. Convert it to use HTMX. The route should return HTML fragments instead of JSON. Add an infinite scroll using hx-trigger='revealed' on a sentinel element at the bottom of the list. Show me both the updated Flask route and the HTML template."

More Things HTMX Makes Easy

Search and infinite scroll are the headline examples, but HTMX handles a whole class of interactions that would otherwise require JavaScript:

Inline Editing

Display a value — click it — it becomes an input field. Save — it goes back to display mode. Classic inline editing, no JavaScript:

<!-- Display mode -->
<span
  hx-get="/edit/username"
  hx-trigger="click"
  hx-target="this"
  hx-swap="outerHTML"
>
  johndoe
</span>
<!-- Server returns this when clicked (edit mode) -->
<form
  hx-put="/save/username"
  hx-target="this"
  hx-swap="outerHTML"
>
  <input type="text" name="username" value="johndoe" autofocus>
  <button type="submit">Save</button>
  <button
    hx-get="/display/username"
    hx-target="this"
    hx-swap="outerHTML"
  >Cancel</button>
</form>

Click the username, it becomes a form. Submit, the server saves it and returns the display version. Cancel, the server returns the display version without saving. The server holds all the logic. HTMX just swaps the HTML.

Live Form Validation

<input
  type="email"
  name="email"
  hx-post="/validate/email"
  hx-trigger="blur"
  hx-target="#email-error"
>
<span id="email-error"></span>

When the user tabs away from the email field (blur), HTMX posts to /validate/email. The server checks if that email is already taken and returns either an empty string or an error message. No JavaScript validation logic. No regex patterns to debug. The server does the check.

Delete Without Page Reload

<div id="task-42" class="task-item">
  <p>Call the supplier about the order</p>
  <button
    hx-delete="/tasks/42"
    hx-target="#task-42"
    hx-swap="outerHTML"
    hx-confirm="Delete this task?"
  >
    Delete
  </button>
</div>

Click Delete, HTMX confirms (browser native confirm dialog), sends DELETE /tasks/42, the server deletes it and returns an empty string, and HTMX replaces the whole #task-42 element with nothing. The item vanishes. No page reload. No JavaScript.

Active Tabs and Accordion Content

<nav class="tabs">
  <button hx-get="/tabs/overview" hx-target="#tab-content">Overview</button>
  <button hx-get="/tabs/specs"    hx-target="#tab-content">Specs</button>
  <button hx-get="/tabs/reviews"  hx-target="#tab-content">Reviews</button>
</nav>

<div id="tab-content">
  <!-- Server-rendered tab content loads here -->
</div>

Click a tab, HTMX fetches the content for that tab, drops it in. No JavaScript state. No "active tab" variable to track. The server renders the right content, the browser displays it.

HTMX vs React: Which Should You Use?

This is the question everyone asks, and the honest answer is: they're tools for different jobs. Picking the wrong one makes your life harder.

If you want the full picture on what React is and how it works, read our guide to React. The short version: React builds interactive UIs on the browser, manages a lot of state in JavaScript, and is excellent when your app needs to feel like a desktop application — instant updates, complex local state, real-time collaboration.

Here's a practical breakdown of when to pick which:

Use HTMX when:

  • You already have a server-rendered app (Python, Ruby, PHP, Go, Node with templates) and want to add interactivity without rewriting it
  • Your interactivity is mostly "request data, display data" — search, filtering, pagination, forms
  • You don't have much JavaScript experience and don't want to learn a framework right now
  • You want something your AI tool can generate reliably without the complex state management bugs
  • SEO matters — server-rendered HTML is inherently better for search engines than JavaScript-heavy single-page apps
  • You're building an internal tool, admin dashboard, or content site where the UX doesn't need to feel like a native app

Use React when:

  • Your app needs to feel instant — no network roundtrip between interactions (think a spreadsheet, a drawing tool, a real-time game)
  • Multiple parts of the UI update simultaneously in response to the same user action
  • You're building a complex single-page application with lots of local state — a calendar, a kanban board, a chat app
  • Your team already knows React and you need to hire developers who do too
  • You're using a framework like Next.js that builds on React and provides structure you need

The honest middle ground: Most apps built by solo vibe coders and small teams don't need React. They need forms that submit without page reloads, search that feels snappy, and tables that sort and filter. HTMX handles all of that. The instinct to reach for React comes from the fact that React dominates the internet's conversation about frontend development — but it doesn't mean React is the right choice for your specific project.

A useful heuristic: If your app mainly displays and updates data, HTMX is probably the right call. If your app is the UI — where the interface itself is the product — React might be worth the complexity.

How AI Tools Handle HTMX

Here's something that makes HTMX especially valuable for vibe coders: AI tools generate it well. Reliably well. Better than most JavaScript framework code.

The reason is the same reason HTMX is easier for humans — the syntax is declarative and constrained. You're working with a defined set of attributes (hx-get, hx-post, hx-trigger, hx-target, hx-swap) that each have a limited set of valid values. There's not a lot of room for creative mistakes.

Compare this to asking an AI to write JavaScript for the same search-as-you-type feature. The AI has to make a dozen small decisions: Which event to listen to? How to debounce? How to cancel pending requests? How to update the DOM safely? Each decision is a potential bug, and the AI doesn't always make the right ones — especially for things like race conditions (what happens if the user types fast and an earlier request comes back after a later one?).

HTMX handles race conditions automatically. It cancels in-flight requests when a new one is triggered from the same element. You don't have to tell it to. The AI doesn't have to generate that logic. It's not a decision anymore.

Prompts that work well with HTMX:

"I have a Flask route that returns a list of orders as JSON. Rewrite it to return an HTML fragment, and write the HTMX attributes for a search input that filters orders as the user types, with a 400ms delay. Target the div with id 'order-list'."


"Add inline editing to this user profile page using HTMX. When the user clicks on their name, replace it with an input field. On save, PUT to /profile/name and swap the form back to display mode with the updated value."


"Build a delete button using hx-delete that removes the list item from the DOM without a page reload. Include an hx-confirm dialog. Use outerHTML swap."

When you prompt AI tools for HTMX features, be specific about the four key attributes — which HTTP method, what triggers it, what the target element is, and which swap strategy to use. Concrete attribute descriptions lead to correct code.

Setting Up HTMX: Genuinely This Simple

One of the biggest advantages of HTMX is that the setup is almost nothing compared to a JavaScript framework. There's no npm install. No bundler. No node_modules folder that takes up 400MB.

You add one script tag to your HTML:

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

That's it. HTMX is now active on your page. All your hx-* attributes work.

For production, you'd typically download the file and serve it yourself (so you're not depending on unpkg being available), or install it via npm if you already have a build pipeline. But for prototyping and development, the CDN approach works perfectly.

If you want to understand what JavaScript is doing under the hood when HTMX runs, that background helps — but you absolutely do not need it to use HTMX. The whole point is that you're working in HTML, not JavaScript.

What Your Server Needs to Know

HTMX sends a few extra HTTP headers with every request that your server can use:

  • HX-Request: true — always present, lets you know the request came from HTMX
  • HX-Trigger — the id or name of the element that triggered the request
  • HX-Target — the id of the target element
  • HX-Current-URL — the URL of the page making the request

The most useful one is HX-Request. You can check for it on your server to return a full page layout for normal browser requests, but just the HTML fragment for HTMX requests — so the same URL works for both direct navigation and HTMX partial updates:

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

    # HTMX request: return just the results fragment
    if request.headers.get("HX-Request"):
        return render_template("partials/results.html", results=results)

    # Normal browser request: return the full page
    return render_template("search.html", results=results, query=query)

One route. Two behaviors. HTMX gets the fragment. Direct browser navigation gets the full page. This pattern — sometimes called "partial responses" — is how HTMX apps stay functional even without JavaScript, which is good for accessibility, SEO, and just being a solid web app.

What HTMX Can't Do (Be Honest With Yourself)

HTMX is excellent, but it has real limits. Knowing them upfront saves you from trying to force it into places it doesn't fit.

Instant UI feedback requires a round trip. Every HTMX interaction has to go to your server and come back. On a fast connection with a fast server, that's 50–200ms — barely perceptible. But if your server is slow, or the user's connection is poor, there's a noticeable lag on every interaction. React can update the UI instantly (optimistic updates) and sync with the server in the background. HTMX can't do that without JavaScript.

Complex dependent state is awkward. Imagine a multi-step form where the options in step 3 depend on what you selected in step 1 and step 2. In React, you manage that state in the browser. In HTMX, you're either sending a request to the server on every change (round trips for every selection) or storing state in hidden inputs and sending it all at once. Neither is terrible, but it's messier than React's state management for genuinely complex flows.

Real-time multiplayer or live push requires something extra. If you need the server to push updates to the browser without the user doing anything — like a live dashboard refreshing or a chat where new messages appear — HTMX can poll (fire a request every N seconds), but that's not true real-time. For genuine WebSocket-based push, you'd use HTMX's WebSocket extension or switch to a different approach.

Offline or progressive web app features aren't in scope. If you need your app to work offline or cache data locally, HTMX doesn't touch that. You'd need JavaScript service workers.

None of these are reasons not to use HTMX for the right project. They're just honest edges of the tool's capability. Most web apps don't need any of those things — but if yours does, know it upfront.

Why the Vibe Coding Community Loves HTMX

HTMX has 40,000+ GitHub stars and a loyal Hacker News following for reasons that go beyond the technical.

The web got complicated in a particular direction over the last decade. The assumption became: interactive = React (or Vue, or Angular, or Svelte, or the next thing). And with React comes a build pipeline, a component hierarchy, state management, client-side routing, and a whole ecosystem you have to learn. Even if you're using AI to generate the code, you still have to understand enough to guide it — and to debug it when it goes wrong.

HTMX is a bet on a different direction: that HTML is powerful, that servers are good at their job, and that most interactivity doesn't require all that complexity. It's not a throwback to 2005 — the library is actively maintained, works with any server language, and handles modern patterns elegantly. It's more like a correction to an overcorrection.

For vibe coders specifically, HTMX aligns with how AI tools are most effective. You describe interactions in terms of HTML attributes. The AI generates attributes. You paste them in. You test. You iterate. The feedback loop is fast. The error surface is small. And when something doesn't work, the debugging is usually simple: check that the right element is targeted, check what your server is actually returning, check that the trigger event fires.

Compare that to debugging a React component with stale closure bugs, prop drilling issues, or useEffect dependency array problems. Those debugging sessions can eat hours even with AI assistance. HTMX bugs are usually visible in one HTTP response.

Frequently Asked Questions

What is HTMX in simple terms?

HTMX is a small JavaScript library (about 14kb) that you include with a single script tag. Once it's on your page, you can add special attributes — like hx-get, hx-post, and hx-trigger — to any HTML element. Those attributes let the element make server requests and update part of the page, without you writing a single line of JavaScript. A button that loads content. An input that searches as you type. A row that deletes itself. All from HTML attributes.

Do I need to know JavaScript to use HTMX?

No — that's the whole point. HTMX lets you build interactive features using only HTML attributes. You write your front-end HTML with hx-* attributes, and you write your back end in any server language you like (Python, Ruby, Go, PHP, Node with templates — anything that can return HTML). You don't need to understand JavaScript frameworks, npm, bundlers, or build pipelines. You do need a server that can respond with HTML fragments, but that's simpler than it sounds.

Is HTMX better than React?

They solve different problems. HTMX is better for server-rendered apps where you want interactivity without a full JavaScript framework — live search, form submissions, tabbed content, inline editing. React is better for single-page applications with lots of local state that needs to feel instantly reactive — collaborative tools, complex dashboards, apps where the interface itself is the product. For most vibe coder projects — internal tools, content sites, simple CRUD apps — HTMX is genuinely simpler and gets the job done faster.

How does HTMX handle the server side?

HTMX doesn't care what language your server uses. When a user triggers an HTMX request, HTMX sends a normal HTTP request (GET, POST, PUT, DELETE) to your server. The difference from traditional approaches is that your server should respond with HTML, not JSON. HTMX takes that HTML and drops it into the page wherever you pointed it. Your server can be Python/Flask, Node/Express, Ruby/Rails, Go, PHP — anything that can return HTML. The HTMX library also sends helpful headers like HX-Request: true so your server knows to return a fragment instead of a full page.

Can AI tools like Claude generate good HTMX code?

Yes — HTMX is one of the things AI coding tools handle especially well. The attribute-based syntax is constrained and declarative, which means there's less room for the subtle logic errors that trip AI tools up with complex JavaScript. HTMX also handles tricky things automatically — like canceling in-flight requests when a new one fires — so the AI doesn't have to generate that logic. Just tell your AI what interaction you want, specify the HTTP method, trigger event, target element, and swap strategy, and the generated code is usually correct on the first try.

What to Learn Next

Now that you understand what HTMX does and when to reach for it:

  • What Is HTML? — HTMX builds directly on top of HTML. If you want to understand why HTMX works the way it does, understanding HTML's request model is the foundation.
  • What Is JavaScript? — HTMX replaces a lot of JavaScript, but knowing what JavaScript actually does helps you understand when HTMX is enough and when you need to go further.
  • What Is React? — The full breakdown of what React is, how it works, and the cases where it's genuinely the right tool. Understanding React makes the HTMX vs. React decision a lot clearer.