Skip to content
HTMX Getting Started — Complete Beginner's Guide with Practical Examples

HTMX Getting Started — Complete Beginner's Guide with Practical Examples

DodaTech Updated Jun 6, 2026 10 min read

In this HTMX getting started guide, you’ll build dynamic web apps using hx-get, hx-post, hx-target, and hx-swap without writing JavaScript.

What You’ll Learn

  • What HTMX is and why it simplifies frontend development
  • How to install HTMX via CDN or npm
  • How to use core attributes: hx-get, hx-post, hx-target, hx-swap
  • How to show loading indicators with hx-indicator
  • How to build a complete AJAX-driven page without a single line of JS

Why HTMX Matters

Most modern web apps send JSON data from the server and then use JavaScript to render it on the page. HTMX flips this model: the server sends raw HTML, and HTMX swaps it into the page. The result is simpler code, faster development, and fewer bugs.

This approach is used in security tools like Durga Antivirus Pro to load real-time scan results without full page reloads. Instead of writing complex JavaScript to parse JSON and update the DOM, the server sends pre-rendered HTML fragments that swap directly into the dashboard. This makes the UI faster and the code easier to audit for security vulnerabilities.

Your Learning Path

    graph LR
    A[HTML & CSS Basics] --> B[HTMX Getting Started]
    B --> C[Requests & Triggers]
    C --> D[Swapping & Transitions]
    D --> E[Advanced Patterns]
    style B fill:#3b82f6,color:#fff,stroke:#2563eb
    style A fill:#e2e8f0,stroke:#94a3b8
    style C fill:#e2e8f0,stroke:#94a3b8
    style D fill:#e2e8f0,stroke:#94a3b8
    style E fill:#e2e8f0,stroke:#94a3b8
  
Prerequisites: You should know basic HTML (tags, attributes, ids) and have a simple backend that returns HTML (or use the provided mock server in the sandbox). No JavaScript experience needed.

What is HTMX?

HTMX is a lightweight JavaScript library that lets you make AJAX requests, trigger CSS transitions, and use WebSockets directly from HTML attributes. Think of it as adding superpowers to your HTML tags — any element can now fetch data from the server and update the page automatically.

The core idea: Any HTML element can issue an HTTP request, and the server’s HTML response replaces content on the page. No JSON, no virtual DOM, no build step.

<!-- This button fetches data and updates the page — no JS required -->
<button hx-get="/api/time" hx-target="#clock">
  Get Current Time
</button>
<div id="clock"></div>

When you click the button, HTMX sends a GET request to /api/time. The server returns an HTML snippet (like <span>2:45 PM</span>), and HTMX inserts it into #clock.

How to Install HTMX

Two ways to add HTMX to your project:

CDN — Add one script tag and you’re ready:

<script src="https://cdn.jsdelivr.net/npm/htmx.org@2.x.x/dist/htmx.min.js"></script>

npm — For build-tool projects:

npm install htmx.org

Then import it:

import 'htmx.org'

That’s it. No bundler config, no JSX, no virtual DOM. HTMX works immediately.

Core Attributes Explained Step by Step

hx-get — Fetch Data from the Server

hx-get tells HTMX to make a GET request when the element is triggered (clicked by default on buttons).

<!-- hx-get: the URL to fetch -->
<!-- hx-target: the element to put the response into -->
<!-- The server must return HTML, not JSON -->
<button hx-get="/api/users" hx-target="#user-list">
  Load Users
</button>
<div id="user-list"></div>

Why use hx-get? Because fetching data on demand is the most common interaction on the web. HTMX makes it a single HTML attribute instead of a dozen lines of JS.

hx-target — Choose Where the Response Goes

Without hx-target, the response replaces the element that made the request. Usually, you want the response to go somewhere else — that’s what hx-target is for.

Target TypeExampleWhat It Does
CSS selectorhx-target="#output"Puts response in element with id output
thishx-target="this"Replaces the element itself
closesthx-target="closest div"Finds nearest ancestor div
nexthx-target="next div"Next sibling div
previoushx-target="previous div"Previous sibling div
findhx-target="find span"First child span
<!-- "this" replaces the button itself -->
<button hx-get="/api/self-destruct" hx-target="this" hx-swap="outerHTML">
  Click to Replace Yourself
</button>

<!-- "closest" finds the nearest matching parent -->
<div class="card">
  <button hx-get="/api/card" hx-target="closest .card" hx-swap="outerHTML">
    Refresh This Card
  </button>
</div>

Why does the target matter? Think of it like a delivery address. Without an address, the package goes back to the sender (the button itself). With a target, you control exactly where new content appears.

hx-post / hx-put / hx-patch / hx-delete — Different HTTP Methods

Each attribute maps to an HTTP method. The most common after hx-get is hx-post for form submissions.

<!-- hx-post sends form data as a POST request -->
<form hx-post="/api/users" hx-target="#user-list">
  <input type="text" name="name" placeholder="Enter name" required>
  <button type="submit">Add User</button>
</form>

<!-- hx-delete removes a resource -->
<button hx-delete="/api/users/1" hx-target="#user-1" hx-swap="outerHTML">
  Delete User
</button>

Why different methods? Because REST conventions expect different HTTP methods for create (POST), read (GET), update (PUT/PATCH), and delete (DELETE). Using the right method keeps your API clean and your server logic predictable.

hx-swap — Control How Content Replaces the Page

The hx-swap attribute determines the insertion strategy. The default is innerHTML (replace content inside the target).

<!-- innerHTML: replace the contents of the target (default) -->
<div hx-get="/api/content" hx-swap="innerHTML">
  This content gets replaced
</div>

<!-- outerHTML: replace the target element itself -->
<div id="old-div" hx-get="/api/replacement" hx-swap="outerHTML">
  This entire div (including its tag) gets replaced
</div>

<!-- beforeend: append as last child (great for lists) -->
<button hx-get="/api/next-item" hx-target="#list" hx-swap="beforeend">
  Add Item
</button>
<ul id="list">
  <li>Existing item</li>
</ul>

<!-- delete: remove the target element -->
<button hx-delete="/api/item/1" hx-swap="delete">
  Remove this item
</button>

<!-- none: execute the request but don't change the page -->
<button hx-post="/api/log-visit" hx-swap="none">
  Log Visit (silent)
</button>

When to use each: Use innerHTML most of the time. Use outerHTML when you need to replace the container itself. Use beforeend for appending items to a list. Use delete after a successful delete action. Use none for background logging or analytics pings.

hx-trigger — What Makes the Request Fire

By default, buttons fire on click and forms on submit. But you can change the trigger with hx-trigger:

<!-- Fire on page load (lazy load content) -->
<div hx-get="/api/initial-data" hx-trigger="load">
  Loading...
</div>

<!-- Fire when input changes (with debounce) -->
<input hx-get="/api/search" hx-trigger="input changed delay:300ms" hx-target="#results">

<!-- Fire every 5 seconds (polling) -->
<div hx-get="/api/status" hx-trigger="every 5s">
  Status: checking...
</div>

<!-- Fire on mouse enter (hover preview) -->
<span hx-get="/api/preview/1" hx-trigger="mouseenter" hx-target="#tooltip">
  Hover for preview
</span>

Why use different triggers? Because different interactions need different timing. A search should fire after the user stops typing (delay), a status indicator should poll regularly (every), and a tooltip should show on hover (mouseenter).

hx-indicator — Show the User Something Is Happening

Loading indicators tell the user their action is being processed. Without them, users might click again or think the site is broken.

<button hx-get="/api/slow-data" hx-target="#result" hx-indicator="#spinner">
  Load Data
</button>
<img id="spinner" class="htmx-indicator" src="/spinner.gif" alt="Loading..." style="display:none;">

<style>
  /* HTMX adds the htmx-request class to the triggering element during the request */
  .htmx-request .htmx-indicator { display: inline; }
  .htmx-indicator { display: none; }
</style>

HTMX adds the htmx-request class to the triggering element while the request is in flight. Your CSS can use this to show/hide the indicator.

Complete AJAX-Driven Page

Here’s everything combined into one working page:

<!DOCTYPE html>
<html>
<head>
  <script src="https://cdn.jsdelivr.net/npm/htmx.org@2.x.x/dist/htmx.min.js"></script>
</head>
<body>
  <h1>User Directory</h1>

  <!-- Live search with debounce -->
  <input type="search"
         name="q"
         hx-get="/api/users"
         hx-trigger="input changed delay:300ms, search"
         hx-target="#user-list"
         placeholder="Search users...">

  <!-- Users load on page load -->
  <div id="user-list">
    <div hx-get="/api/users" hx-trigger="load">
      Loading users...
    </div>
  </div>

  <!-- Pagination -->
  <button hx-get="/api/users?page=2"
          hx-target="#user-list"
          hx-swap="outerHTML">
    Load More
  </button>
</body>
</html>

How this works:

  1. The search input waits 300ms after the last keystroke before fetching results
  2. The user list loads automatically when the page loads (hx-trigger="load")
  3. The “Load More” button fetches the next page and replaces the entire user list
  4. All of this happens without a single line of JavaScript

Common Mistakes

1. Forgetting hx-target

Without hx-target, the response replaces the triggering element. This often breaks the UI because the button disappears or content ends up in the wrong place. Always set hx-target explicitly.

2. Returning JSON Instead of HTML

HTMX expects HTML responses. If your server returns JSON, HTMX will dump raw JSON text into the target element. Make sure your server returns HTML fragments.

// ❌ Wrong: returns JSON
res.json({ name: "Alice", role: "admin" })

// ✅ Correct: returns HTML
res.send('<div class="user">Alice (admin)</div>')

3. Not Handling Loading States

Users get confused when they click and nothing happens. Always add hx-indicator for any request that might take more than 200ms.

4. Using hx-trigger=“load” on Elements Added Dynamically

When HTMX loads new content that contains elements with hx-trigger="load", those triggers fire automatically. This is usually what you want, but it can cause unexpected duplicate requests if you’re not careful.

5. Missing Closing Tags in HTML Responses

Since HTMX swaps raw HTML, missing closing tags or malformed HTML in the server response will break the page layout. Always validate your HTML fragments.

6. Forgetting That hx-swap=“outerHTML” Removes the Target

When you use outerHTML, the target element itself is removed from the DOM. Any event listeners or state attached to it are lost. Use innerHTML if you want to keep the container.

Practice Questions

Q1: What does hx-target="this" do?

A1: It tells HTMX to replace the element that triggered the request with the server response.

Q2: What’s the difference between hx-swap="innerHTML" and hx-swap="outerHTML"?

A2: innerHTML replaces the content inside the target element. outerHTML replaces the target element itself (including its opening and closing tags).

Q3: What happens if you don’t include hx-target?

A3: The response replaces the element that triggered the request (the element with hx-get, hx-post, etc.).

Q4: Why would you use hx-trigger="load"?

A4: To lazy-load content when the page first renders. This is useful for data that isn’t needed immediately but should appear without user interaction.

Q5: What CSS class does HTMX add to the triggering element during a request?

A5: The htmx-request class is added while the request is in flight.

Challenge: Build a “Load More” comments section. Start with 3 comments visible and a button that loads 3 more each time it’s clicked. Use hx-swap="beforeend" to append new comments after existing ones. The server should return 3 comment HTML snippets per request.

FAQ

Does HTMX work without JavaScript at all?

HTMX is a JavaScript library — it requires JS to run. However, you can use progressive enhancement: build forms that work with standard HTML submission first, then add HTMX attributes to enhance the experience for JS-enabled users.

Can I use HTMX with my backend language?

HTMX works with any backend that returns HTML. Django, Laravel, Express.js, Flask, Rails, ASP.NET — if it can return HTML, it works with HTMX.

How is HTMX different from React or Vue?

React and Vue render HTML on the client side using JavaScript. HTMX sends HTML from the server and swaps it into the page. HTMX is simpler because there’s no client-side state management, no virtual DOM, and no build step.

Is HTMX production-ready?

Yes. HTMX is stable, well-tested, and used in high-traffic production applications. Its ecosystem includes extensions for WebSockets, SSE, form validation, and more.

Try It Yourself

Copy this into an HTML file and open it in a browser. The mock server simulates real backend responses.

▶ Try It Yourself Edit the code and click Run

What’s Next

Now that you’ve mastered the basics, continue your learning path:

TutorialWhat You’ll Learn
HTMX — Requests & TriggersMaster hx-trigger, hx-vals, hx-headers, and request flow control
HTMX — Swapping & TransitionsCSS animations, morphing, OOB swaps, and the ViewTransitions API
HTMX — Advanced PatternsWebSockets, SSE, history management, and production deployment

Related topics: Learn how HTMX compares to Alpine.js for client-side interactivity, or pair it with Tailwind CSS for rapid UI development. For security-focused apps, combine HTMX with Flask to build tools like network scanners and log analyzers — the same approach used in Doda Browser’s internal monitoring dashboards.

Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro. These tutorials reflect real-world patterns used in production security and productivity tools.

What’s Next

Congratulations on completing this Htmx Getting Started tutorial! Here’s where to go from here:

  • Practice daily — Consistency is more important than long study sessions
  • Build a project — Apply what you learned by building something real
  • Explore related topics — Check out other tutorials in the same category
  • Join the community — Discuss with other learners and share your progress

Remember: every expert was once a beginner. Keep coding!

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro