htmx 4.0 is under construction — migration guide

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:

  1. Browser asks Reason for deletion?.
  2. Cancel aborts.
  3. Any answer (even empty) fires a cancelable htmx:prompt event carrying it in event.detail.prompt.
  4. If nothing cancels it, htmx sends the request with the HX-Prompt header 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>