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
}
}
<!DOCTYPE html>
<html>
<head>
<title>MyApp</title>
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
<%= javascript_importmap_tags %>
</head>
<body data-controller="confirm" data-action="turbo:before-fetch-request@document->confirm#show">
<%= yield %>
<!-- Custom confirmation modal -->
<div data-confirm-target="modal" class="fixed inset-0 bg-black bg-opacity-50 hidden flex items-center justify-center z-50">
<div class="bg-white rounded-lg p-6 max-w-md mx-4">
<h3 class="text-lg font-semibold mb-4">Confirm Action</h3>
<p data-confirm-target="message" class="text-gray-700 mb-6"></p>
<div class="flex gap-3 justify-end">
<button data-action="confirm#cancel" class="btn btn-secondary">Cancel</button>
<button data-action="confirm#proceed" class="btn btn-danger">Confirm</button>
</div>
</div>
</div>
</body>
</html>
<div class="post">
<h1><%= @post.title %></h1>
<%= simple_format @post.body %>
<% if can? :destroy, @post %>
<%= button_to "Delete Post",
post_path(@post),
method: :delete,
class: "btn btn-danger",
form: {
data: {
turbo_confirm: "This will permanently delete '#{@post.title}'. This action cannot be undone."
}
} %>
<% end %>
</div>
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.