javascript erb 76 lines · 3 tabs

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.


Related snips

ruby
class CommentsController < ApplicationController
  before_action :set_post

  def create
    @comment = @post.comments.build(comment_params)

System test: asserting Turbo Stream responses

rails hotwire turbo
by codesnips 4 tabs
ruby
class PostsController < ApplicationController
  def index
    @posts = Post.includes(:author)
                 .order(created_at: :desc)
                 .page(params[:page])
                 .per(10)

Turbo Frames: infinite scroll with lazy-loading frame

rails turbo hotwire
by codesnips 4 tabs
erb
<form data-controller="query-sync" data-action="change->query-sync#apply">
  <select name="status" class="rounded border p-2">
    <option value="">Any</option>
    <option value="open">Open</option>
    <option value="closed">Closed</option>
  </select>

Filter UI that syncs query params via Stimulus (no front-end router)

rails hotwire stimulus
by Henry Kim 2 tabs
ruby
class TasksController < ApplicationController
  def destroy
    @task = Task.find(params[:id])
    @task.destroy!

    respond_to do |format|

Remove deleted items instantly with turbo_stream.remove

rails hotwire turbo
by Henry Kim 2 tabs
javascript
import { Controller } from "@hotwired/stimulus"
import Mousetrap from "mousetrap"

export default class extends Controller {
  connect() {
    // Global shortcuts

Keyboard shortcuts with Stimulus and Mousetrap

stimulus javascript ux
by Jordan Lee 2 tabs
javascript
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = ["form"]
  static values = { delay: { type: Number, default: 250 } }

Debounced live search with Stimulus + Turbo Streams

rails hotwire stimulus
by codesnips 4 tabs

Share this code

Here's the card — post it anywhere.

Turbo confirmation dialogs with custom modal — share card
Link copied