Swap one table row with htmx instead of reloading the page
Use htmx to replace a single table row with an HTML fragment on click, avoiding a full page reload.
17 Feb 2026, 20:13 UTC

One button, one row, no reload
You click “Mark complete” on a task row. The whole page reloads. Your scroll position resets, any unsaved text in a nearby input disappears, and the browser re-fetches assets you already have. The useful takeaway: htmx can request only the changed markup and swap it into the element you name, leaving the rest of the page untouched.
Return an HTML fragment, not a page
htmx issues an AJAX request—a background HTTP call made by the browser without reloading the document—and puts the server’s response into the DOM, which is the browser’s live tree of page elements. The response should be an HTML fragment: a piece of markup such as a single table row, not a full `` document and not JSON.
For a task list, the endpoint might return this fragment:
<tr id="task-42" class="done">
<td>Write the release notes</td>
<td>
<span>Completed</span>
<button hx-get="/tasks/42/toggle" hx-target="#task-42" hx-swap="outerHTML">
Reopen
</button>
</td>
</tr>
Markup that swaps the row
Place the htmx attributes on the button inside the row:
<tr id="task-42">
<td>Write the release notes</td>
<td>
<button
hx-get="/tasks/42/toggle"
hx-target="#task-42"
hx-swap="outerHTML"
hx-trigger="click"
hx-indicator="#spinner-42"
>
Mark complete
</button>
<img id="spinner-42" src="/spinner.gif" style="display:none" />
</td>
</tr>
What each attribute does:
hx-get: sends a GET request when the trigger fires.hx-target: names the element that receives the response, here the row itself.hx-swap: controls insertion.outerHTMLreplaces the entire row, including the button.innerHTMLwould inject the response inside the button, which is not what we want.hx-trigger: starts the request on a click.hx-indicator: shows an element while the request is pending. htmx adds thehtmx-requestclass to that element, so you can reveal a spinner with CSS.
A caution about outerHTML: it removes the triggering element from the DOM. Any custom JavaScript listeners attached directly to that button disappear unless you use event delegation or reattach them after the swap. htmx’s own attributes continue to work because it re-processes the new markup.
Verify the swap and know the limits
Open DevTools, go to the Network panel, click the button, and check:
- The request URL matches your toggle endpoint.
- The response Content-Type is
text/html. - The body is a fragment such as a
<tr>, not a full page beginning with `<html>`. - Only the targeted row changes; the browser does not perform a full reload.
You can check the endpoint directly from a terminal:
curl -i https://example.com/tasks/42/toggle
Where: your local terminal or a machine with network access to the server. Permissions: none beyond ordinary network access. Placeholder: replace https://example.com/tasks/42/toggle with your actual toggle route. Expected checks: HTTP 200, Content-Type: text/html, and a body that starts with <tr rather than `<!DOCTYPE html>` or `<html>`. Risk: if the server returns a full document, htmx will nest it inside the target and break layout.
You can also add a temporary outline to the target:
#task-42 { outline: 3px solid orange; }
After the request completes, the new content should appear inside that boundary.
htmx does not enforce CSRF, authentication, or authorization. The server must validate every request as it would any other HTTP endpoint. Returning a full document into hx-target can nest tags and break CSS layout. Avoid polling with hx-trigger="every 5s" unless you truly need it; it creates steady traffic even when nothing changed.
Because marking a task complete changes server state, include an undo path. The same toggle endpoint can switch the row back, or you can render a “Reopen” button in the fragment that issues its own hx-get request.
Start with one row
Pick one button, return a fragment, and verify it in the Network panel before converting the rest of the page. Check your htmx version in your import or lock file: core attributes like hx-get are stable across htmx 1.x, but event names, extension APIs, and swap modifiers can differ in later major releases.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.