htmx: Build Interactive Web Apps with HTML · Beyond the Basics
Choosing htmx: Architecture Decisions
Decide when htmx is the right architecture, when a JavaScript framework is, and how to combine them in one product.
What you'll build
A decision record for a new product with three parts, an admin area, a public catalogue and a drag-and-drop schedule planner, choosing the right approach for each.
The idea
htmx moves state and rendering to the server. That's a strength when the server is the source of truth and the UI mostly shows and edits that data. It's a weakness when the UI must react instantly to every mouse move, work offline, or share a JSON API with mobile apps.
| Question | Points to htmx | Points to a JS framework |
|---|---|---|
| Where does the truth live? | the server database | the browser (offline, local edits) |
| How often does the UI change without the server? | rarely | constantly (drag, draw, edit) |
| Team skills | strong backend | strong frontend |
| Other clients (mobile)? | none, or separate API | shared JSON API already needed |
| Long-term maintenance | fewer dependencies, no build | larger ecosystem, more tooling |
Mixing is normal: htmx for most pages, and a React, Vue or Svelte island for the one complex widget, mounted on a
<div> in a server-rendered page.
The code
A hybrid page: htmx handles the server-driven list; a tiny client-side widget (standing in for a framework island) handles instant interaction and saves through a normal htmx request:
<h4>Today's schedule</h4>
<ul id="slots" hx-get="/slots" hx-trigger="load, slotsChanged from:body"></ul>
<div id="planner">
<input id="time" type="range" min="8" max="18" value="9">
<output id="shown">09:00</output>
<button id="book">Book this slot</button>
</div>
<script>
// The "island": instant feedback in the browser, no request per movement.
const time = document.getElementById("time"), shown = document.getElementById("shown");
time.addEventListener("input", () => { shown.textContent = String(time.value).padStart(2, "0") + ":00"; });
document.getElementById("book").addEventListener("click", () =>
htmx.ajax("POST", "/slots", { values: { hour: time.value }, swap: "none", source: "#book" }));
</script>
<!-- The pretend server: on a real site this part is your backend (Python, PHP, Node, Go...) -->
<script>
const booked = [10];
server.get("/slots", () => booked.sort((a, b) => a - b).map((h) => `<li>${String(h).padStart(2, "0")}:00 booked</li>`).join(""));
server.post("/slots", (req) => { booked.push(Number(req.body.hour)); return { body: "", headers: { "HX-Trigger": "slotsChanged" } }; });
</script>
For the example product: the admin area and catalogue are classic htmx territory (forms, tables, search, SEO-friendly pages); the schedule planner is an island with a client-side library that saves through the same server endpoints.
Common mistakes
- Choosing by hype in either direction. Decide per feature, using the questions above.
- Rebuilding complex client-side widgets with dozens of htmx requests per second.
- Building a JSON API and server templates for the same screens "just in case": two sources of truth.
- Forgetting that htmx pages are server-rendered by default, which helps SEO and first load.
Key points
- htmx fits server-owned data and form/table-heavy UIs.
- Frameworks fit highly interactive, offline or multi-client UIs.
- Islands let both live in one product.
Further reading: htmx essay: When should you use hypermedia? · htmx JavaScript API: htmx.ajax
Practice questions
Name two signs that a feature is a poor fit for htmx alone.
Answer
The UI changes continuously without the server (drag-and-drop, drawing), or it must work offline / share a JSON API with other clients.
What is an 'island' in a mostly htmx app?
Answer
A self-contained client-side widget (React, Vue, Svelte or plain JS) mounted inside a server-rendered page, talking to the same server.
How did the island in the example tell the htmx list to refresh?
Answer
Its POST response carried HX-Trigger: slotsChanged, and the list listens with hx-trigger="slotsChanged from:body".
Open in the app to mark lessons complete, take notes and track your progress.