The hx-prompt extension restores htmx 2’s hx-prompt attribute.
Before each request, it pops up a browser prompt. The answer becomes the HX-Prompt header. If the user cancels, the request never fires.
Installing
<script src="https://cdn.jsdelivr.net/npm/htmx.org@next/dist/htmx.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/htmx.org@next/dist/ext/hx-prompt.js"></script>
Usage
<button hx-delete="/items/1" hx-prompt="Reason for deletion?"> Delete </button>
On click:
- Browser asks
Reason for deletion?. - Cancel aborts.
- Any answer (even empty) fires a cancelable
htmx:promptevent carrying it inevent.detail.prompt. - If nothing cancels it, htmx sends the request with the
HX-Promptheader set to the answer.
htmx:prompt event
The htmx:prompt event fires on the source element after a valid answer.
Listen with hx-on::prompt and call event.preventDefault() to abort:
<button hx-delete="/items/1" hx-prompt="Reason?" hx-on::prompt="if (prompt.length < 3) event.preventDefault()"> Delete </button>
event.detail carries { prompt, target }. hx-on exposes it directly, so they’re in scope without unwrapping.
Inheritance
The hx-prompt attribute supports inheritance.
<div hx-prompt:inherited="Reason?"> <button hx-delete="/items/1">Delete</button> <button hx-delete="/items/2">Delete</button> </div>
Composing with hx-confirm
hx-prompt runs before hx-confirm. Both must pass for the request to proceed:
<button hx-delete="/items/1" hx-prompt="Reason?" hx-confirm="Are you sure?"> Delete </button>
If the user cancels the prompt, the confirm dialog is not shown.
Custom Dialogs
Assign a function to window.htmxPrompt to use a custom (synchronous) dialog.
// receives the question, returns the answer string or null to cancel window.htmxPrompt = (question) => myCustomSyncDialog(question);
The extension uses window.prompt by default.
Server-Side Example
HX-Prompt is a regular request header. Read it however your server reads headers.
def delete_item(request, id): reason = request.headers.get("HX-Prompt", "") Item.objects.filter(id=id).delete(reason=reason) return HttpResponse("") # empty response removes the row
Without the extension
If you’d rather skip the extension, a single hx-on can do the same thing:
<button hx-delete="/items/1" hx-on::config:request="ctx.request.headers['HX-Prompt'] = prompt('Reason?') ?? event.preventDefault()"> Delete </button>