How it works
Wrap a table in a <form>. Each row has a checkbox, and an action bar appears when any are checked. Clicking a row toggles its checkbox.
<form id="user-list" hx-target="#user-list" hx-swap="outerHTML"> <div id="action-bar" class="hidden"> <span>With selected:</span> <button hx-post="/activate">Activate</button> <button hx-post="/deactivate">Deactivate</button> <button hx-post="/delete">Delete</button> </div> <table> <thead> <tr> <th><input type="checkbox" class="select-all"></th> <th>Name</th> <th>Email</th> <th>Status</th> </tr> </thead> <tbody> <tr> <td><input type="checkbox" name="selected" value="joe@smith.org"></td> <td>Joe Smith</td> <td>joe@smith.org</td> <td>Active</td> </tr> ... </tbody> </table> </form>
hx-target="#user-list"andhx-swap="outerHTML"on the form mean every action replaces the entire form with the server’s response.- Each action button uses
hx-postto a different endpoint. Only checkedname="selected"values are submitted.
Clickable rows
Make the whole row a click target so users don’t have to aim for the small checkbox:
<tr _="on click if event.target.tagName !== 'INPUT' toggle @checked on the <input[name='selected']/> in me then send change to the <input[name='selected']/> in me"> <td><input type="checkbox" name="selected" value="joe@smith.org"></td> ... </tr>
The if event.target.tagName !== 'INPUT' guard prevents double-toggling when clicking directly on the checkbox. Highlighting the selected row is pure CSS:
tr:has(input:checked) { background: var(--selected-bg); }
Conditional action bar
Show the action bar only when at least one checkbox is checked. With CSS :has():
.action-bar { display: none; } form:has(input[name="selected"]:checked) .action-bar { display: flex; }
Or with hyperscript on the <tbody> (for browsers without :has() support):
<tbody _="on change from <input[name='selected']/> if (<input[name='selected']:checked/> in closest <form/>).length > 0 show #action-bar else hide #action-bar end">
Select all
A checkbox in the header toggles all row checkboxes:
<input type="checkbox" _="on change set checked to my.checked then for cb in <input[name='selected']/> in closest <form/> set cb.checked to checked then send checkChange to closest <form/>">
Server response
The server processes the selected emails, performs the bulk action, and re-renders the full table. Selections are cleared and statuses reflect the update:
<!-- POST /activate with selected=joe@smith.org&selected=kim@yee.org --> <form id="user-list" hx-target="#user-list" hx-swap="outerHTML"> <!-- ...updated table rows... --> <output>Activated 1 user</output> </form>