Turbo confirmation dialogs with custom modal

Jordan Lee Jan 2026
3 tabs
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = ["modal", "message", "confirm", "cancel"]
  static values = {
    message: String
  }

  show(event) {
    // Prevent the default Turbo confirm
    event.preventDefault()

    const message = event.detail?.message || this.messageValue || "Are you sure?"
    this.messageTarget.textContent = message
    this.modalTarget.classList.remove("hidden")

    // Store the original event to replay it
    this.pendingEvent = event
  }

  proceed() {
    this.modalTarget.classList.add("hidden")

    // Resume the Turbo request
    if (this.pendingEvent) {
      this.pendingEvent.detail.resume()
      this.pendingEvent = null
    }
  }

  cancel() {
    this.modalTarget.classList.add("hidden")
    this.pendingEvent = null
  }
}
3 files · javascript, erb Explain with highlit

The default browser confirm() dialog is ugly and doesn't match your design system. Turbo provides hooks to intercept confirmation dialogs and show custom modals instead. I listen for the turbo:before-fetch-request event, check if the element has data-turbo-confirm, and prevent the request while showing a styled modal. When the user confirms, I programmatically trigger the original action. This pattern works for delete links, dangerous actions, or any operation requiring explicit confirmation. The custom dialog can include rich HTML, additional context, or async operations before proceeding. I also use this hook for optimistic UI updates that revert if the server request fails.