HTMX Confirm Dialogs

A confirm dialog asks the user to verify a destructive or important action before the request fires. Without confirmation, a misclick on a Delete button can cause data loss. HTMX provides a built-in confirmation mechanism with the hx-confirm attribute, and it also supports custom dialog patterns for more polished user interfaces.

The Built-In hx-confirm Attribute

The simplest confirmation in HTMX is one attribute:

<button
  hx-delete="/posts/15"
  hx-target="#post-15"
  hx-swap="outerHTML"
  hx-confirm="Are you sure you want to delete this post? This cannot be undone.">
  Delete Post
</button>
  Flow:

  User clicks [Delete Post]
       |
       v
  Browser shows native confirm dialog:
  ┌────────────────────────────────────┐
  │ Are you sure you want to delete    │
  │ this post? This cannot be undone.  │
  │                                    │
  │  [Cancel]          [OK]            │
  └────────────────────────────────────┘
       |                   |
  User clicks Cancel   User clicks OK
       |                   |
  Nothing happens     HTMX sends DELETE /posts/15
                           |
                      Server deletes post
                           |
                      #post-15 disappears from page

The native browser dialog is accessible, keyboard-navigable, and requires zero CSS. Its main limitation is that it looks different on every browser and cannot be styled.

hx-confirm on Any Element

The hx-confirm attribute works on any element that has an HTMX request attribute:

<!-- On a link -->
<a href="#" hx-post="/archive/42" hx-confirm="Archive this item?">Archive</a>

<!-- On a form's submit button -->
<form hx-post="/transfer-funds" hx-target="#status">
  <input type="number" name="amount">
  <button type="submit" hx-confirm="Transfer these funds?">Transfer</button>
</form>

Custom Confirm Dialog

For a styled, branded dialog, you override HTMX's built-in confirm behavior using the htmx:confirm event. HTMX fires this event before any confirmed request. You intercept it, show your own dialog, and call detail.issueRequest() when the user confirms.

<!-- Custom modal HTML -->
<div id="confirm-modal" style="display:none; position:fixed; top:0; left:0;
     width:100%; height:100%; background:rgba(0,0,0,0.5);
     align-items:center; justify-content:center;">
  <div style="background:#fff; padding:30px; border-radius:8px; max-width:400px;">
    <p id="confirm-message"></p>
    <button id="confirm-yes">Yes, proceed</button>
    <button id="confirm-no">Cancel</button>
  </div>
</div>

<!-- Trigger button -->
<button
  hx-delete="/accounts/7"
  hx-target="#account-7"
  hx-swap="outerHTML"
  hx-confirm="Close this account permanently?">
  Close Account
</button>

<script>
let pendingRequest = null;

htmx.on('htmx:confirm', function(event) {
    event.preventDefault();   // Stop HTMX from using the native dialog
    pendingRequest = event.detail;

    document.getElementById('confirm-message').textContent = event.detail.question;

    const modal = document.getElementById('confirm-modal');
    modal.style.display = 'flex';
});

document.getElementById('confirm-yes').onclick = function() {
    document.getElementById('confirm-modal').style.display = 'none';
    if (pendingRequest) {
        pendingRequest.issueRequest(true);  // Proceed with the HTMX request
        pendingRequest = null;
    }
};

document.getElementById('confirm-no').onclick = function() {
    document.getElementById('confirm-modal').style.display = 'none';
    pendingRequest = null;
};
</script>
  Custom dialog flow:

  User clicks [Close Account]
       |
       v
  htmx:confirm event fires
  JavaScript intercepts it, shows custom modal:

  ┌─────────────────────────────────────────┐
  │  Close this account permanently?        │
  │                                         │
  │  [Yes, proceed]       [Cancel]          │
  └─────────────────────────────────────────┘

  "Yes, proceed" → issueRequest(true) → DELETE fires
  "Cancel"       → modal hides, nothing happens

Two-Step Confirmation With Text Input

For irreversible actions — like deleting an account or purging data — require the user to type a confirmation phrase. This pattern prevents accidental confirmations:

<div id="delete-zone">
  <p>Type DELETE to confirm account removal:</p>
  <input type="text" id="confirm-text" placeholder="Type DELETE">
  <button
    hx-delete="/account"
    hx-target="body"
    hx-swap="innerHTML"
    hx-confirm="This will permanently delete your account."
    onclick="return checkConfirm()">
    Delete My Account
  </button>
</div>

<script>
function checkConfirm() {
    if (document.getElementById('confirm-text').value !== 'DELETE') {
        alert('Please type DELETE to confirm.');
        return false;   // Prevent HTMX from firing
    }
    return true;
}
</script>

Best Practices for Confirm Dialogs

GuidelineWhy
Use plain, specific language"Delete this comment?" is clearer than "Confirm action"
Put the destructive action on the rightUsers read left to right; Cancel on the left prevents misclicks
Make the Cancel button prominentEncourages caution; the safe path should be easy to take
Do not overuse confirmationsConfirmation fatigue causes users to click OK without reading
Reserve for irreversible actions onlyActions with an undo option do not need a confirm dialog

Confirm for Bulk Actions

<button
  hx-post="/posts/delete-all"
  hx-target="#post-list"
  hx-swap="innerHTML"
  hx-confirm="Delete all 47 posts? This cannot be undone.">
  Delete All Posts
</button>

Including the item count in the confirmation message ("all 47 posts") makes users pause and think before confirming. It is a small detail that prevents a lot of support tickets.

Key Takeaway

Use hx-confirm to add a native browser confirmation dialog to any HTMX request. For a styled custom dialog, listen to the htmx:confirm event in JavaScript, show your own modal, and call detail.issueRequest(true) when the user confirms. Reserve confirm dialogs for truly destructive or irreversible actions. For the most dangerous operations, require the user to type a confirmation phrase before proceeding.

Leave a Comment

Your email address will not be published. Required fields are marked *