css javascript erb 123 lines · 3 tabs

Stimulus: resilient confirmation for destructive actions

Shared by codesnips Jan 2026
3 tabs
.confirm-dialog {
  border: none;
  border-radius: 12px;
  padding: 1.5rem;
  max-width: 26rem;
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.25);
}

.confirm-dialog::backdrop {
  background: rgba(15, 23, 42, 0.55);
  backdrop-filter: blur(2px);
}

.confirm-dialog menu {
  display: flex;
  justify-content: flex-end;
  gap: 0.5rem;
  margin: 1.25rem 0 0;
  padding: 0;
}

.confirm-dialog input {
  display: block;
  width: 100%;
  margin-top: 0.35rem;
  padding: 0.5rem;
}

.btn--danger[disabled] {
  opacity: 0.5;
  cursor: not-allowed;
}
3 files · css, javascript, erb Explain with highlit

This snippet shows a resilient confirmation pattern for destructive actions built as a Stimulus controller, backed by a Rails view that wires it up declaratively. The goal is a confirmation flow that never leaves a delete button in a broken state: if JavaScript fails to load, the plain form still submits; if it loads, the controller intercepts the submission and gates it behind a dialog.

The confirm_controller.js tab treats the native <dialog> element as the confirmation surface rather than the blocking window.confirm, which cannot be styled and freezes the page. In submit(event), the controller calls event.preventDefault() only after confirming the element is trusted, then opens the dialog with showModal(). The key resilience detail is that the original submit event is captured and replayed: this.pendingSubmitter remembers which button was clicked so the correct formmethod/formaction survives the round trip. For high-risk actions the controller supports a typed guard via the phrase value — the user must type an exact confirmation string before confirmTarget enables, defeating muscle-memory clicks.

The validate handler compares the trimmed input against phraseValue and toggles the disabled attribute, and confirmed() finally submits the real form with requestSubmit(this.pendingSubmitter) so Turbo and native validation both still fire. disconnect() guards against leaks by closing any open dialog, which matters because Turbo caches and restores pages without full reloads.

The _delete_button.html.erb tab is the progressive-enhancement half: the form carries data-controller, data-action on submit, and the data-confirm-phrase-value, so the same markup degrades gracefully. Note that data-turbo-confirm is deliberately omitted because this controller replaces it with a richer flow.

The confirm_dialog.css tab styles the ::backdrop and constrains focus visually. This approach is worth reaching for when destructive actions need real accessibility, custom copy, or a typed guard that window.confirm and data-turbo-confirm cannot provide, while keeping a working no-JS fallback. The main pitfall is forgetting that dialog.close() fires a close event, so the controller must distinguish cancel from confirm to avoid double submission.


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 Post < ApplicationRecord
  belongs_to :author, class_name: 'User'
  has_many :comments, dependent: :destroy

  scope :published, -> { where.not(published_at: nil).where('published_at <= ?', Time.current) }
  scope :draft, -> { where(published_at: nil) }

ActiveRecord scopes for reusable query logic

rails activerecord patterns
by Alex Kumar 1 tab
ruby
module Api
  module V1
    class UsersController < BaseController
      def show
        user = User.includes(:profile).find(params[:id])

ETags for conditional requests and caching

rails caching http-caching
by Alex Kumar 1 tab
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
html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Semantic HTML Example</title>

Semantic HTML5 elements and accessibility best practices

html html5 semantics
by Alex Chang 2 tabs
ruby
require "csv"

class PeopleCsvStream
  include Enumerable

  HEADERS = %w[id full_name email signed_up_at plan].freeze

Resilient CSV Export as a Streamed Response

rails performance streaming
by codesnips 3 tabs

Share this code

Here's the card — post it anywhere.

Stimulus: resilient confirmation for destructive actions — share card
Link copied