HTMX Getting Started — Complete Beginner's Guide with Practical Examples
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
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.orgThen 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 Type | Example | What It Does |
|---|---|---|
| CSS selector | hx-target="#output" | Puts response in element with id output |
this | hx-target="this" | Replaces the element itself |
closest | hx-target="closest div" | Finds nearest ancestor div |
next | hx-target="next div" | Next sibling div |
previous | hx-target="previous div" | Previous sibling div |
find | hx-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:
- The search input waits 300ms after the last keystroke before fetching results
- The user list loads automatically when the page loads (
hx-trigger="load") - The “Load More” button fetches the next page and replaces the entire user list
- 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
Try It Yourself
Copy this into an HTML file and open it in a browser. The mock server simulates real backend responses.
What’s Next
Now that you’ve mastered the basics, continue your learning path:
| Tutorial | What You’ll Learn |
|---|---|
| HTMX — Requests & Triggers | Master hx-trigger, hx-vals, hx-headers, and request flow control |
| HTMX — Swapping & Transitions | CSS animations, morphing, OOB swaps, and the ViewTransitions API |
| HTMX — Advanced Patterns | WebSockets, 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