Confirmation Rails Components

Confirmation components are used to confirm an action or a decision. They can be used to confirm a deletion, a change of status, a change of password, etc.

Installation

1. Stimulus Controller Setup

Start by adding the following controller to your project:

import { Controller } from "@hotwired/stimulus";

export default class extends Controller {
  static targets = ["input", "confirmButton", "checkbox"];
  static values = {
    confirmText: { type: String, default: "" },
    caseSensitive: { type: Boolean, default: false },
  };

  connect() {
    this.confirmed = false;
    this.confirmTextArray = this.parseConfirmText();
    this.updateConfirmButton();

    // Add form submission prevention
    const form = this.element.closest("form");
    if (form) {
      this.boundHandleSubmit = this.handleSubmit.bind(this);
      form.addEventListener("submit", this.boundHandleSubmit);
    }

    // Add input listeners
    if (this.hasInputTarget) {
      this.inputTarget.addEventListener("input", () => this.checkConfirmation());
      this.inputTarget.addEventListener("keydown", (event) => {
        if (event.key === "Enter") {
          if (this.confirmed) {
            event.preventDefault();
            this.confirm();
          } else {
            // Prevent form submission when confirmation is not valid
            event.preventDefault();
          }
        }
      });
    }

    // Add checkbox listeners
    if (this.hasCheckboxTarget) {
      this.checkboxTargets.forEach((checkbox) => {
        checkbox.addEventListener("change", () => this.checkConfirmation());
      });
    }
  }

  disconnect() {
    // Remove form submission listener
    const form = this.element.closest("form");
    if (form && this.boundHandleSubmit) {
      form.removeEventListener("submit", this.boundHandleSubmit);
    }

    if (this.hasInputTarget) {
      this.inputTarget.removeEventListener("input", () => this.checkConfirmation());
    }

    if (this.hasCheckboxTarget) {
      this.checkboxTargets.forEach((checkbox) => {
        checkbox.removeEventListener("change", () => this.checkConfirmation());
      });
    }
  }

  parseConfirmText() {
    if (!this.confirmTextValue) return [];

    // Simple comma-separated approach for arrays
    if (this.confirmTextValue.includes(",")) {
      return this.confirmTextValue
        .split(",")
        .map((item) => item.trim())
        .filter((item) => item.length > 0);
    }

    return [this.confirmTextValue];
  }

  checkConfirmation() {
    // Check text confirmation
    let textConfirmed = false;
    if (this.hasInputTarget && this.confirmTextArray.length > 0) {
      const inputValue = this.caseSensitiveValue ? this.inputTarget.value : this.inputTarget.value.toLowerCase();

      if (this.confirmTextArray.length === 1) {
        const confirmValue = this.caseSensitiveValue
          ? this.confirmTextArray[0]
          : this.confirmTextArray[0].toLowerCase();
        textConfirmed = inputValue === confirmValue;
      } else {
        textConfirmed = this.confirmTextArray.some((text) => {
          const confirmValue = this.caseSensitiveValue ? text : text.toLowerCase();
          return inputValue === confirmValue;
        });
      }
    } else {
      textConfirmed = this.hasInputTarget ? this.inputTarget.value.trim().length > 0 : true;
    }

    // Check checkbox confirmation
    let checkboxConfirmed = true;
    if (this.hasCheckboxTarget && this.checkboxTargets.length > 0) {
      checkboxConfirmed = this.checkboxTargets.every((checkbox) => checkbox.checked);
    }

    this.confirmed = textConfirmed && checkboxConfirmed;
    this.updateConfirmButton();
  }

  updateConfirmButton() {
    if (this.hasConfirmButtonTarget) {
      this.confirmButtonTarget.disabled = !this.confirmed;

      if (this.confirmed) {
        this.confirmButtonTarget.classList.remove("opacity-50", "cursor-not-allowed");
      } else {
        this.confirmButtonTarget.classList.add("opacity-50", "cursor-not-allowed");
      }
    }
  }

  confirm() {
    if (!this.confirmed) return false;

    const beforeConfirmEvent = new CustomEvent("confirmation:before", {
      detail: {
        value: this.getConfirmationValue(),
        controller: this,
      },
      cancelable: true,
    });

    if (!this.element.dispatchEvent(beforeConfirmEvent)) {
      return false;
    }

    this.performConfirmation();

    this.element.dispatchEvent(
      new CustomEvent("confirmation:confirmed", {
        detail: {
          value: this.getConfirmationValue(),
          controller: this,
        },
      })
    );

    return true;
  }

  handleSubmit(event) {
    // Only allow form submission if confirmation is valid
    if (!this.confirmed) {
      event.preventDefault();
      event.stopPropagation();
      return false;
    }
  }

  cancel() {
    this.reset();
    this.element.dispatchEvent(
      new CustomEvent("confirmation:cancelled", {
        detail: { controller: this },
      })
    );
  }

  reset() {
    this.confirmed = false;

    if (this.hasInputTarget) {
      this.inputTarget.value = "";
    }

    if (this.hasCheckboxTarget) {
      this.checkboxTargets.forEach((checkbox) => {
        checkbox.checked = false;
      });
    }

    this.updateConfirmButton();
  }

  performConfirmation() {
    const form = this.element.closest("form");
    if (form) {
      form.submit();
    }
  }

  getConfirmationValue() {
    const textValue = this.hasInputTarget ? this.inputTarget.value : "";
    const checkboxValues = this.hasCheckboxTarget
      ? this.checkboxTargets.map((checkbox) => ({
          id: checkbox.id,
          checked: checkbox.checked,
        }))
      : [];

    return {
      text: textValue,
      checkboxes: checkboxValues,
    };
  }
}

Examples

Basic Text Confirmation

A simple confirmation component where users must type a specific word or phrase to confirm a destructive action like deleting an account.

Delete Account

This action cannot be undone. This will permanently delete your account and remove your data from our servers.

<div class="max-w-md bg-white dark:bg-neutral-800 border border-black/10 dark:border-white/10 rounded-xl shadow-xs overflow-hidden px-4 py-5 sm:p-6">
  <div class="mb-4">
    <h3 class="text-lg font-semibold text-neutral-900 dark:text-white mb-2">Delete Account</h3>
    <p class="text-sm text-neutral-600 dark:text-neutral-400">This action cannot be undone. This will permanently delete your account and remove your data from our servers.</p>
  </div>

  <form data-controller="confirmation" data-confirmation-confirm-text-value="DELETE">

    <div class="mb-4">
      <label for="confirm-delete" class="block text-sm font-medium text-neutral-700 dark:text-neutral-300 mb-2">
        Type <span class="inline rounded-md border border-black/10 bg-white px-1 py-0.5 font-mono text-neutral-800 dark:border-white/10 dark:bg-neutral-900 dark:text-neutral-200">DELETE</span> to confirm:
      </label>
      <input type="text"
             id="confirm-delete"
             data-confirmation-target="input"
             class="form-control"
             autocomplete="off"
             placeholder="Type DELETE to confirm">
    </div>

    <div class="flex gap-3 justify-end">
      <button type="button"
              data-action="click->confirmation#cancel"
              class="flex items-center justify-center gap-1.5 rounded-lg border border-neutral-200 bg-white/90 px-3.5 py-2 text-sm font-medium whitespace-nowrap text-neutral-800 shadow-xs transition-all duration-100 ease-in-out select-none hover:bg-neutral-50 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-neutral-600 disabled:cursor-not-allowed disabled:opacity-50 dark:border-neutral-700 dark:bg-neutral-800/50 dark:text-neutral-50 dark:hover:bg-neutral-700/50 dark:focus-visible:outline-neutral-200">
        Cancel
      </button>
      <button type="button"
              data-confirmation-target="confirmButton"
              data-action="click->confirmation#confirm"
              disabled
              class="flex items-center justify-center gap-1.5 rounded-lg border border-red-300/30 bg-red-600 px-3.5 py-2 text-sm font-medium whitespace-nowrap text-white shadow-sm transition-all duration-100 ease-in-out select-none hover:bg-red-500 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-neutral-600 disabled:cursor-not-allowed disabled:opacity-50 dark:focus-visible:outline-neutral-200">
        Delete Account
      </button>
    </div>
  </form>
</div>

Case-Sensitive Text Confirmation

A confirmation component with case-sensitive matching, perfect for confirming project names or other exact text requirements.

Reset Database

This will permanently delete all data in your database. Please type the project name to confirm this destructive action.

Warning

This action is case-sensitive and cannot be undone.
<div class="max-w-md bg-white dark:bg-neutral-800 border border-black/10 dark:border-white/10 rounded-xl shadow-xs overflow-hidden px-4 py-5 sm:p-6">
  <div class="mb-4">
    <h3 class="text-lg font-semibold text-neutral-900 dark:text-white mb-2">Reset Database</h3>
    <p class="text-sm text-neutral-600 dark:text-neutral-400">This will permanently delete all data in your database. Please type the project name to confirm this destructive action.</p>
  </div>

  <form data-controller="confirmation"
        data-confirmation-confirm-text-value="rails-blocks-production"
        data-confirmation-case-sensitive-value="true">

    <div class="mb-4">
      <label for="project-name" class="block text-sm font-medium text-neutral-700 dark:text-neutral-300 mb-2">
        Type <span class="inline rounded-md border border-black/10 bg-white px-1 py-0.5 font-mono text-neutral-800 dark:border-white/10 dark:bg-neutral-900 dark:text-neutral-200">rails-blocks-production</span> to confirm:
      </label>
      <input type="text"
             id="project-name"
             data-confirmation-target="input"
             class="form-control"
             autocomplete="off"
             placeholder="Enter project name exactly as shown">
    </div>

    <div class="mb-4 rounded-xl border border-yellow-200 bg-yellow-50 p-4 dark:border-yellow-800 dark:bg-yellow-900/20">
      <div class="grid grid-cols-[auto_1fr] gap-2 items-start">
        <div class="flex items-center h-full">
          <svg xmlns="http://www.w3.org/2000/svg" class="text-yellow-500 dark:text-yellow-400" width="18" height="18" viewBox="0 0 18 18">
            <g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor">
              <path d="M7.63796 3.48996L2.21295 12.89C1.60795 13.9399 2.36395 15.25 3.57495 15.25H14.425C15.636 15.25 16.392 13.9399 15.787 12.89L10.362 3.48996C9.75696 2.44996 8.24296 2.44996 7.63796 3.48996Z"></path>
              <path d="M9 6.75V9.75"></path>
              <path d="M9 13.5C8.448 13.5 8 13.05 8 12.5C8 11.95 8.448 11.5 9 11.5C9.552 11.5 10 11.9501 10 12.5C10 13.0499 9.552 13.5 9 13.5Z" fill="currentColor" data-stroke="none" stroke="none"></path>
            </g>
          </svg>
        </div>
        <h3 class="text-sm font-medium text-amber-800 dark:text-amber-200">
          Warning
        </h3>
        <div></div>
        <div class="text-sm text-amber-700 dark:text-amber-300">
          This action is case-sensitive and cannot be undone.
        </div>
      </div>
    </div>

    <div class="flex gap-3 justify-end">
      <button type="button"
              data-action="click->confirmation#cancel"
              class="flex items-center justify-center gap-1.5 rounded-lg border border-neutral-200 bg-white/90 px-3.5 py-2 text-sm font-medium whitespace-nowrap text-neutral-800 shadow-xs transition-all duration-100 ease-in-out select-none hover:bg-neutral-50 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-neutral-600 disabled:cursor-not-allowed disabled:opacity-50 dark:border-neutral-700 dark:bg-neutral-800/50 dark:text-neutral-50 dark:hover:bg-neutral-700/50 dark:focus-visible:outline-neutral-200">
        Cancel
      </button>
      <button type="button"
              data-confirmation-target="confirmButton"
              data-action="click->confirmation#confirm"
              disabled
              class="flex items-center justify-center gap-1.5 rounded-lg border border-red-300/30 bg-red-600 px-3.5 py-2 text-sm font-medium whitespace-nowrap text-white shadow-sm transition-all duration-100 ease-in-out select-none hover:bg-red-500 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-neutral-600 disabled:cursor-not-allowed disabled:opacity-50 dark:focus-visible:outline-neutral-200">
        Reset Database
      </button>
    </div>
  </form>
</div>

Array of possible text values

A confirmation component that accepts multiple text values to confirm. Use this when you need to confirm one of several possible text values.

Delete Account

This action cannot be undone. This will permanently delete your account and remove your data from our servers.

DELETE REMOVE DESTROY
<div class="max-w-md bg-white dark:bg-neutral-800 border border-black/10 dark:border-white/10 rounded-xl shadow-xs overflow-hidden px-4 py-5 sm:p-6">
  <div class="mb-4">
    <h3 class="text-lg font-semibold text-neutral-900 dark:text-white mb-2">Delete Account</h3>
    <p class="text-sm text-neutral-600 dark:text-neutral-400">This action cannot be undone. This will permanently delete your account and remove your data from our servers.</p>
  </div>

  <form data-controller="confirmation"
        data-confirmation-confirm-text-value="<%= %w(DELETE REMOVE DESTROY).join(', ') %>">

    <div class="mb-4">
      <label for="confirm-delete" class="block text-sm font-medium text-neutral-700 dark:text-neutral-300 mb-2">
        Type one of the following to confirm:
      </label>
      <div class="flex flex-wrap gap-2 mb-3">
        <span class="inline rounded-md border border-black/10 bg-white px-2 py-1 font-mono text-sm text-neutral-800 dark:border-white/10 dark:bg-neutral-900 dark:text-neutral-200">DELETE</span>
        <span class="inline rounded-md border border-black/10 bg-white px-2 py-1 font-mono text-sm text-neutral-800 dark:border-white/10 dark:bg-neutral-900 dark:text-neutral-200">REMOVE</span>
        <span class="inline rounded-md border border-black/10 bg-white px-2 py-1 font-mono text-sm text-neutral-800 dark:border-white/10 dark:bg-neutral-900 dark:text-neutral-200">DESTROY</span>
      </div>
      <input type="text"
             id="confirm-delete"
             data-confirmation-target="input"
             class="form-control"
             autocomplete="off"
             placeholder="Type DELETE, REMOVE, or DESTROY to confirm">
    </div>

    <div class="flex gap-3 justify-end">
      <button type="button"
              data-action="click->confirmation#cancel"
              class="flex items-center justify-center gap-1.5 rounded-lg border border-neutral-200 bg-white/90 px-3.5 py-2 text-sm font-medium whitespace-nowrap text-neutral-800 shadow-xs transition-all duration-100 ease-in-out select-none hover:bg-neutral-50 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-neutral-600 disabled:cursor-not-allowed disabled:opacity-50 dark:border-neutral-700 dark:bg-neutral-800/50 dark:text-neutral-50 dark:hover:bg-neutral-700/50 dark:focus-visible:outline-neutral-200">
        Cancel
      </button>
      <button type="button"
              data-confirmation-target="confirmButton"
              data-action="click->confirmation#confirm"
              disabled
              class="flex items-center justify-center gap-1.5 rounded-lg border border-red-300/30 bg-red-600 px-3.5 py-2 text-sm font-medium whitespace-nowrap text-white shadow-sm transition-all duration-100 ease-in-out select-none hover:bg-red-500 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-neutral-600 disabled:cursor-not-allowed disabled:opacity-50 dark:focus-visible:outline-neutral-200">
        Delete Account
      </button>
    </div>
  </form>
</div>

Any text confirmation

A confirmation component that accepts any text value to confirm. This is useful for confirming any text value, such as a project name or a confirmation code.

Create new project

<div class="w-full max-w-md bg-white dark:bg-neutral-800 border border-black/10 dark:border-white/10 rounded-xl shadow-xs overflow-hidden px-4 py-5 sm:p-6">
  <div class="mb-4">
    <h3 class="text-lg font-semibold text-neutral-900 dark:text-white mb-2">Create new project</h3>
  </div>

  <form data-controller="confirmation"
        data-confirmation-confirm-text-value="">

    <div class="mb-4">
      <label for="project-name" class="block text-sm font-medium text-neutral-700 dark:text-neutral-300 mb-2">
        Enter the project name:
      </label>
      <input type="text"
             id="project-name"
             data-confirmation-target="input"
             class="form-control"
             autocomplete="off"
             placeholder="Type any text to confirm...">
    </div>

    <div class="flex gap-3 justify-end">
      <button type="button"
              data-action="click->confirmation#cancel"
              class="flex items-center justify-center gap-1.5 rounded-lg border border-neutral-200 bg-white/90 px-3.5 py-2 text-sm font-medium whitespace-nowrap text-neutral-800 shadow-xs transition-all duration-100 ease-in-out select-none hover:bg-neutral-50 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-neutral-600 disabled:cursor-not-allowed disabled:opacity-50 dark:border-neutral-700 dark:bg-neutral-800/50 dark:text-neutral-50 dark:hover:bg-neutral-700/50 dark:focus-visible:outline-neutral-200">
        Cancel
      </button>
      <button type="button"
              data-confirmation-target="confirmButton"
              data-action="click->confirmation#confirm"
              disabled
              class="flex items-center justify-center gap-1.5 rounded-lg border border-neutral-400/30 bg-neutral-800 px-3.5 py-2 text-sm font-medium whitespace-nowrap text-white shadow-sm transition-all duration-100 ease-in-out select-none hover:bg-neutral-700 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-neutral-600 disabled:cursor-not-allowed disabled:opacity-50 dark:bg-white dark:text-neutral-800 dark:hover:bg-neutral-100 dark:focus-visible:outline-neutral-200">
        Create Project
      </button>
    </div>
  </form>
</div>

Multi-Step Confirmation (Multiple)

A confirmation component that requires users to acknowledge multiple consequences by selecting checkboxes. Perfect for complex actions with multiple implications.

Transfer Project Ownership

Please type "TRANSFER" AND select all checkboxes below to confirm you understand the consequences.

Confirm transfer consequences
<div class="max-w-md bg-white dark:bg-neutral-800 border border-black/10 dark:border-white/10 rounded-xl shadow-xs overflow-hidden px-4 py-5 sm:p-6">
  <div class="mb-4">
    <h3 class="text-lg font-semibold text-neutral-900 dark:text-white mb-2">Transfer Project Ownership</h3>
    <p class="text-sm text-neutral-600 dark:text-neutral-400">Please type "TRANSFER" AND select all checkboxes below to confirm you understand the consequences.</p>
  </div>

  <form data-controller="confirmation"
        data-confirmation-confirm-text-value="TRANSFER">

    <div class="mb-4">
      <label for="transfer-input" class="block text-sm font-medium text-neutral-900 dark:text-white mb-2">
        Type <span class="inline rounded-md border border-black/10 bg-white px-1 py-0.5 font-mono text-neutral-800 dark:border-white/10 dark:bg-neutral-900 dark:text-neutral-200">TRANSFER</span> to confirm
      </label>
      <input type="text"
             id="transfer-input"
             data-confirmation-target="input"
             placeholder="Type TRANSFER to confirm"
             autocomplete="off"
             class="form-control">
    </div>

    <fieldset class="space-y-3 max-w-sm w-full mb-6">
      <legend class="sr-only">Confirm transfer consequences</legend>

      <label for="ownership-checkbox" class="relative py-3 px-4 flex items-center font-medium bg-white text-neutral-800 rounded-xl cursor-pointer ring-1 ring-neutral-200 has-[:checked]:ring-2 has-[:checked]:ring-neutral-400 dark:bg-neutral-700/50 dark:text-neutral-200 dark:ring-neutral-700 dark:has-[:checked]:ring-neutral-400 has-[:checked]:bg-neutral-100 has-[:checked]:text-neutral-900 dark:has-[:checked]:bg-neutral-600/60 dark:has-[:checked]:text-white"
             data-confirmation-item="ownership"
             role="checkbox"
             aria-checked="false"
             tabindex="0">
        <input type="checkbox" id="ownership-checkbox" class="absolute left-4" data-confirmation-target="checkbox">
        <div class="flex-1 ml-8">
          <div class="flex items-center justify-between">
            <div>
              <h3 class="text-sm font-semibold dark:text-neutral-100">I understand that ownership will be transferred immediately</h3>
              <p class="text-xs text-neutral-500 dark:text-neutral-400">You will lose admin access to this project</p>
            </div>
          </div>
        </div>
      </label>

      <label for="billing-checkbox" class="relative py-3 px-4 flex items-center font-medium bg-white text-neutral-800 rounded-xl cursor-pointer ring-1 ring-neutral-200 has-[:checked]:ring-2 has-[:checked]:ring-neutral-400 dark:bg-neutral-700/50 dark:text-neutral-200 dark:ring-neutral-700 dark:has-[:checked]:ring-neutral-400 has-[:checked]:bg-neutral-100 has-[:checked]:text-neutral-900 dark:has-[:checked]:bg-neutral-600/60 dark:has-[:checked]:text-white"
             data-confirmation-item="billing"
             role="checkbox"
             aria-checked="false"
             tabindex="0">
        <input type="checkbox" id="billing-checkbox" class="absolute left-4" data-confirmation-target="checkbox">
        <div class="flex-1 ml-8">
          <div class="flex items-center justify-between">
            <div>
              <h3 class="text-sm font-semibold dark:text-neutral-100">I understand billing responsibility will transfer</h3>
              <p class="text-xs text-neutral-500 dark:text-neutral-400">All future charges will be billed to the new owner</p>
            </div>
          </div>
        </div>
      </label>

      <label for="access-checkbox" class="relative py-3 px-4 flex items-center font-medium bg-white text-neutral-800 rounded-xl cursor-pointer ring-1 ring-neutral-200 has-[:checked]:ring-2 has-[:checked]:ring-neutral-400 dark:bg-neutral-700/50 dark:text-neutral-200 dark:ring-neutral-700 dark:has-[:checked]:ring-neutral-400 has-[:checked]:bg-neutral-100 has-[:checked]:text-neutral-900 dark:has-[:checked]:bg-neutral-600/60 dark:has-[:checked]:text-white"
             data-confirmation-item="access"
             role="checkbox"
             aria-checked="false"
             tabindex="0">
        <input type="checkbox" id="access-checkbox" class="absolute left-4" data-confirmation-target="checkbox">
        <div class="flex-1 ml-8">
          <div class="flex items-center justify-between">
            <div>
              <h3 class="text-sm font-semibold dark:text-neutral-100">I understand this action cannot be undone</h3>
              <p class="text-xs text-neutral-500 dark:text-neutral-400">You will need to request access from the new owner</p>
            </div>
          </div>
        </div>
      </label>
    </fieldset>

    <div class="flex gap-3 justify-end">
      <button type="button"
              data-action="click->confirmation#cancel"
              class="flex items-center justify-center gap-1.5 rounded-lg border border-neutral-200 bg-white/90 px-3.5 py-2 text-sm font-medium whitespace-nowrap text-neutral-800 shadow-xs transition-all duration-100 ease-in-out select-none hover:bg-neutral-50 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-neutral-600 disabled:cursor-not-allowed disabled:opacity-50 dark:border-neutral-700 dark:bg-neutral-800/50 dark:text-neutral-50 dark:hover:bg-neutral-700/50 dark:focus-visible:outline-neutral-200">
        Cancel
      </button>
      <button type="button"
              data-confirmation-target="confirmButton"
              data-action="click->confirmation#confirm"
              disabled
              class="flex items-center justify-center gap-1.5 rounded-lg border border-neutral-400/30 bg-neutral-800 px-3.5 py-2 text-sm font-medium whitespace-nowrap text-white shadow-sm transition-all duration-100 ease-in-out select-none hover:bg-neutral-700 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-neutral-600 disabled:cursor-not-allowed disabled:opacity-50 dark:bg-white dark:text-neutral-800 dark:hover:bg-neutral-100 dark:focus-visible:outline-neutral-200 opacity-50 cursor-not-allowed">
        Transfer Ownership
      </button>
    </div>
  </form>
</div>

A confirmation component that opens in a modal dialog, providing a focused experience for critical confirmations with detailed warnings and consequences.

Delete Workspace

This action cannot be undone

This will permanently delete:

  • All projects and files in this workspace
  • All team member access and permissions
  • Billing history and subscription data
<div class="max-w-md mx-auto">
  <div data-controller="modal">

    <button class="flex items-center justify-center gap-1.5 rounded-lg border border-red-300/30 bg-red-600 px-3.5 py-2 text-sm font-medium whitespace-nowrap text-white shadow-sm transition-all duration-100 ease-in-out select-none hover:bg-red-500 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-neutral-600 disabled:cursor-not-allowed disabled:opacity-50 dark:focus-visible:outline-neutral-200"
            data-action="click->modal#open:prevent">
      Delete Workspace
    </button>

    <dialog class="modal max-w-[100vw-2rem] sm:max-w-lg bg-transparent rounded-xl w-full lg:rounded-2xl z-50 border border-black/10 dark:border-white/10 bg-white dark:bg-neutral-800 small-scrollbar focus-visible:outline-neutral-600 dark:focus-visible:outline-neutral-200"
            data-modal-target="dialog"
            data-action="mousedown->modal#backdropClose">

      <div class="sm:max-w-3xl row-start-2 w-full rounded-xl lg:rounded-2xl bg-white p-6 dark:bg-neutral-800 forced-colors:outline">
        <button type="button"
                data-action="modal#close:prevent"
                class="z-10 p-1.5 absolute right-4 top-5 rounded-full opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-hidden hover:bg-neutral-500/15 active:bg-neutral-500/25 disabled:pointer-events-none">
          <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="h-4 w-4">
            <line x1="18" x2="6" y1="6" y2="18"></line>
            <line x1="6" x2="18" y1="6" y2="18"></line>
          </svg>
          <span class="sr-only">Close</span>
        </button>

        <div class="mb-4">
          <div class="flex items-center gap-3 mb-3">
            <div class="flex-shrink-0 w-10 h-10 bg-red-100 rounded-full flex items-center justify-center dark:bg-red-900/30 border border-red-200 dark:border-red-800">
              <svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-red-600 dark:text-red-400" width="20" height="20" viewBox="0 0 20 20">
                <g fill="currentColor">
                  <path fill-rule="evenodd" d="M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495ZM10 5a.75.75 0 0 1 .75.75v3.5a.75.75 0 0 1-1.5 0v-3.5A.75.75 0 0 1 10 5Zm0 9a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z" clip-rule="evenodd"/>
                </g>
              </svg>
            </div>
            <div>
              <h2 class="text-lg font-semibold text-neutral-900 dark:text-white">Delete Workspace</h2>
              <p class="text-sm text-neutral-600 dark:text-neutral-400">This action cannot be undone</p>
            </div>
          </div>

          <div class="bg-red-50 border border-red-200 rounded-lg p-4 mb-4 dark:bg-red-900/20 dark:border-red-800">
            <h4 class="text-sm font-medium text-red-800 dark:text-red-200 mb-2">This will permanently delete:</h4>
            <ul class="text-sm text-red-700 dark:text-red-300 space-y-1 list-decimal list-inside">
              <li class="items-center list-item">
                All projects and files in this workspace
              </li>
              <li class="items-center list-item">
                All team member access and permissions
              </li>
              <li class="items-center list-item">
                Billing history and subscription data
              </li>
            </ul>
          </div>
        </div>

        <form data-controller="confirmation"
              data-confirmation-confirm-text-value="PERMANENTLY DELETE">
          <div class="mb-6">
            <label for="delete-confirmation" class="block text-sm font-medium text-neutral-700 dark:text-neutral-300 mb-2">
              Type <span class="inline rounded-md border border-black/10 bg-white px-1 py-0.5 font-mono text-neutral-800 dark:border-white/10 dark:bg-neutral-900 dark:text-neutral-200">PERMANENTLY DELETE</span> to confirm:
            </label>
            <input type="text"
                   id="delete-confirmation"
                   data-confirmation-target="input"
                   class="form-control"
                   autocomplete="off"
                   placeholder="Type PERMANENTLY DELETE to confirm"
                   autofocus>
          </div>

          <div class="flex gap-3 justify-end">
            <button type="button"
                    data-action="click->modal#close:prevent click->confirmation#cancel"
                    class="flex items-center justify-center gap-1.5 rounded-lg border border-neutral-200 bg-white/90 px-3.5 py-2 text-sm font-medium whitespace-nowrap text-neutral-800 shadow-xs transition-all duration-100 ease-in-out select-none hover:bg-neutral-50 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-neutral-600 disabled:cursor-not-allowed disabled:opacity-50 dark:border-neutral-700 dark:bg-neutral-800/50 dark:text-neutral-50 dark:hover:bg-neutral-700/50 dark:focus-visible:outline-neutral-200">
              Cancel
            </button>
            <button type="button"
                    data-confirmation-target="confirmButton"
                    data-action="click->confirmation#confirm click->modal#close:prevent"
                    disabled
                    class="flex items-center justify-center gap-1.5 rounded-lg border border-red-300/30 bg-red-600 px-3.5 py-2 text-sm font-medium whitespace-nowrap text-white shadow-sm transition-all duration-100 ease-in-out select-none hover:bg-red-500 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-neutral-600 disabled:cursor-not-allowed disabled:opacity-50 dark:focus-visible:outline-neutral-200">
              Delete Workspace
            </button>
          </div>
        </form>
      </div>
    </dialog>
  </div>
</div>

Configuration

The confirmation component is powered by a Stimulus controller that provides multiple confirmation modes, visual feedback, and accessibility features.

Controller Setup

Basic confirmation structure with required data attributes:

<div data-controller="confirmation"
     data-confirmation-confirm-text-value="DELETE">
  <input data-confirmation-target="input"
         placeholder="Type DELETE to confirm">
  <button data-confirmation-target="confirmButton"
          data-action="click->confirmation#confirm">
    Confirm
  </button>
</div>

Confirmation Types

Single Text Confirmation

Users must type an exact text string to confirm the action. Case-insensitive by default.

data-confirmation-confirm-text-value="DELETE"

Case-Sensitive Text Confirmation

Users must type an exact text string with precise case matching.

data-confirmation-confirm-text-value="rails-blocks-production" data-confirmation-case-sensitive-value="true"

Multiple Text Confirmation (Comma-Separated)

Users must type one of several possible text strings to confirm. Use comma-separated values.

data-confirmation-confirm-text-value="DELETE, REMOVE, DESTROY"

Any Text Confirmation

Users must type any non-empty text to confirm. Useful for project names or custom confirmations.

data-confirmation-confirm-text-value=""

Checkbox-Only Confirmation

Users must select all required checkboxes to confirm. No text input required.

data-confirmation-confirm-text-value="" (with checkbox targets)

Combined Text and Checkbox Confirmation

Users must type a specific text AND select all required checkboxes to confirm.

data-confirmation-confirm-text-value="TRANSFER" (with checkbox targets)

Configuration Values

Prop Description Type Default
confirmText
The text(s) that must be typed to confirm. Can be a single string, comma-separated values, or empty for any text string ""
caseSensitive
Boolean value to enable case-sensitive text matching boolean false

Targets

Target Description Required
input
The text input field where users type their confirmation Required
confirmButton
The button that triggers the confirmation action Required
checkbox
Checkbox elements that must be selected to confirm (can be multiple) Optional

Actions

Action Description Usage
confirm
Triggers the confirmation process when the button is clicked click->confirmation#confirm
cancel
Resets the confirmation form and cancels the action click->confirmation#cancel

Custom Events

Event Description Cancellable
confirmation:before
Fired before confirmation is processed. Contains confirmation value and controller reference Yes
confirmation:confirmed
Fired when confirmation is successful. Contains confirmation value and controller reference No
confirmation:cancelled
Fired when confirmation is cancelled or reset. Contains controller reference No

Accessibility Features

  • Keyboard Navigation: Full keyboard support with Enter key confirmation
  • Screen Reader Friendly: Clear labels and state announcements
  • Focus Management: Automatic focus handling for modal confirmations

Table of contents

Get notified when new components come out