More Modals Snippets
Confirm Dialog — Type to Confirm HTML CSS JS Snippet
Confirm Dialog · Modals · Plain HTML, CSS & JS · Live preview
What's included
Features
checkPhrase enables Delete only when the input exactly matches the required phrase, forcing deliberate intent.<code>, so changing the displayed name updates the check automatically.backdrop-filter: blur dims and frosts the page; the dialog scales/slides in with transform+opacity for a smooth, export-safe entrance.e.target === overlay), and Escape all route through closeDialog.confirmDelete transitions the button through "Deleting…" and "✓ Deleted", a clear async lifecycle stand-in.busy flag blocks dismissal and repeat clicks during the delete sequence so the action cannot double-fire.About this UI Snippet
Confirm Dialog — Type-to-Confirm Guard, Backdrop Blur & Deleting→Done State

For genuinely destructive actions — deleting a project, dropping a database, closing an account — a plain "Are you sure? [OK]" is too easy to click through on autopilot. The stronger pattern, used by GitHub, Stripe, and Vercel, is type-to-confirm: the user must type the exact name of the thing being destroyed before the confirm button unlocks. That deliberate friction is the point. This snippet implements it in plain HTML, CSS, and vanilla JavaScript: a type-to-confirm guard, a blurred modal overlay, the standard dismissal behaviours, and a deleting→done state.
The type-to-confirm guard
The Delete button starts disabled. checkPhrase runs on every keystroke and compares the trimmed input against the required PHRASE (read from the on-screen <code> so the markup is the source of truth). Only an exact match enables the button and turns the input border green. This forces the user to consciously read and reproduce the resource's name, which all but eliminates accidental deletions and "muscle-memory" confirmations — the entire reason the pattern exists.
A real modal, dismissed the expected ways
The dialog uses role="alertdialog" and aria-modal, with a blurred, dimmed overlay (backdrop-filter: blur). It animates in by scaling and sliding from scale(.94) to scale(1) with opacity — transform/opacity only, so it is smooth and exports cleanly. It can be dismissed three ways: the Cancel button, clicking the backdrop (overlayClick checks e.target === overlay so clicks inside the dialog do not close it), and pressing Escape. Every dismissal path runs closeDialog, which also resets the input and re-disables the button so the guard is fresh next time.
Deleting → done state with a re-entrancy lock
On confirm, confirmDelete switches the button to "Deleting…", then to a green "✓ Deleted", then closes — standing in for an async request. A busy flag locks the dialog during this sequence so Escape, the backdrop, and repeat clicks cannot interrupt or double-fire the action mid-delete. Once finished, closeDialog fully resets the dialog for reuse.
Focus and reset
Opening the dialog focuses the input after the animation so the user can start typing immediately, and closing clears the field, the match style, and the button label — so reopening always presents a clean, locked confirm.
Wire your real delete call into confirmDelete (before the success state) and you have a production-grade safeguard. Pair this with a slide to confirm for an alternative deliberate gesture, a snackbar with undo for reversible deletes, or a modal for non-destructive dialogs.
Step by step
How to Use
- 1Paste HTML, CSS, and JSA red "Delete project" button appears; clicking it opens a blurred modal asking you to type the project name.
- 2See the locked confirmThe Delete button inside the dialog is disabled — you cannot click it until you prove intent.
- 3Type the phraseType
aurora-apiexactly — the input border turns green and the Delete button unlocks the moment it matches. - 4ConfirmClick Delete — it shows "Deleting…", then a green "✓ Deleted", then the dialog closes.
- 5Dismiss itCancel, click the dimmed backdrop, or press Escape to close without deleting; the guard resets for next time.
- 6Wire your actionPut your real delete API call in
confirmDeletebefore the success state — it only runs once the typed name matches.
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
Put your API call inside confirmDelete before showing the success state: keep the busy lock set, await the request, then switch to "✓ Deleted" and close on success. On failure, clear busy, restore the button label, and show an error message instead of closing — so the user can retry without losing the dialog.
The phrase is read from the #cdPhrase element's text, so render the actual resource name into both that <code> and the warning text when you open the dialog. checkPhrase compares against whatever is shown, so per-item dialogs (deleting "billing-prod" vs "aurora-api") work with no code change.
By default this matches exactly (case-sensitive), which is the safest and matches GitHub's behaviour — it forces careful reproduction. If your resource names are case-insensitive, compare with .toLowerCase() on both sides. Keep the trim (already applied) so trailing spaces from autocomplete do not block a correct match.
It uses role="alertdialog" and aria-modal with an aria-labelledby pointing at the title. For full compliance, trap focus inside the dialog while open (cycle Tab between the input and buttons), return focus to the trigger on close, and ensure Escape closes it (it does). The green match state should be paired with the unlocked button, not colour alone, so the cue is not purely visual.
In React, hold open, the typed value, and a busy flag in useState; derive the button's disabled state from value === phrase, and add an Escape useEffect. In Vue, use refs with v-model and a computed match. In Angular, track state on the component and bind [disabled]. The overlay/scale-in CSS and guard logic port unchanged.
Confirm Dialog — Export as HTML, React, Vue, Angular & Tailwind
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Confirm Dialog</title>
<style>
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:system-ui,-apple-system,sans-serif;background:#f1f5f9;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:24px}
.cd-trigger{display:inline-flex;align-items:center;gap:8px;background:#fff;color:#dc2626;border:1.5px solid #fecaca;border-radius:11px;padding:11px 18px;font-size:14px;font-weight:700;cursor:pointer;font-family:inherit;transition:background .15s,border-color .15s}
.cd-trigger:hover{background:#fef2f2;border-color:#fca5a5}
.cd-overlay{position:fixed;inset:0;background:rgba(15,23,42,.5);backdrop-filter:blur(4px);display:flex;align-items:center;justify-content:center;padding:20px;opacity:0;visibility:hidden;transition:opacity .25s,visibility .25s;z-index:50}
.cd-overlay.show{opacity:1;visibility:visible}
.cd-dialog{background:#fff;border-radius:18px;padding:26px;width:100%;max-width:380px;box-shadow:0 25px 60px rgba(0,0,0,.3);transform:scale(.94) translateY(10px);transition:transform .25s cubic-bezier(.2,1.1,.4,1)}
.cd-overlay.show .cd-dialog{transform:scale(1) translateY(0)}
.cd-icon{width:48px;height:48px;border-radius:50%;background:#fee2e2;display:flex;align-items:center;justify-content:center;margin-bottom:14px}
.cd-title{font-size:18px;font-weight:800;color:#1e293b;margin-bottom:8px}
.cd-text{font-size:13px;color:#64748b;line-height:1.55;margin-bottom:18px}
.cd-text strong{color:#1e293b;font-weight:700}
.cd-label{display:block;font-size:12px;color:#475569;margin-bottom:7px}
.cd-label code{background:#f1f5f9;color:#dc2626;font-weight:700;padding:2px 6px;border-radius:5px;font-family:ui-monospace,monospace;font-size:12px}
.cd-input{width:100%;padding:10px 12px;border:1.5px solid #e2e8f0;border-radius:10px;font-size:14px;color:#1e293b;outline:none;font-family:ui-monospace,monospace;transition:border-color .15s,box-shadow .15s;margin-bottom:18px}
.cd-input:focus{border-color:#6366f1;box-shadow:0 0 0 3px rgba(99,102,241,.12)}
.cd-input.match{border-color:#10b981}
.cd-actions{display:flex;gap:10px}
.cd-cancel,.cd-confirm{flex:1;padding:11px;border:none;border-radius:10px;font-size:14px;font-weight:700;cursor:pointer;font-family:inherit;transition:background .15s,opacity .15s}
.cd-cancel{background:#f1f5f9;color:#475569}
.cd-cancel:hover{background:#e2e8f0}
.cd-confirm{background:#dc2626;color:#fff}
.cd-confirm:hover:not(:disabled){background:#b91c1c}
.cd-confirm:disabled{background:#fca5a5;cursor:not-allowed;opacity:.7}
.cd-confirm.done{background:#10b981!important}
</style>
</head>
<body>
<div class="cd-page">
<button class="cd-trigger" onclick="openDialog()">
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2m2 0v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/></svg>
Delete project
</button>
<div class="cd-overlay" id="cdOverlay" onclick="overlayClick(event)">
<div class="cd-dialog" role="alertdialog" aria-modal="true" aria-labelledby="cdTitle">
<div class="cd-icon">
<svg viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="#dc2626" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 9v4m0 4h.01M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0z"/></svg>
</div>
<h2 class="cd-title" id="cdTitle">Delete this project?</h2>
<p class="cd-text">This permanently deletes <strong>aurora-api</strong> and all of its deployments, logs, and secrets. This action <strong>cannot be undone</strong>.</p>
<label class="cd-label">Type <code id="cdPhrase">aurora-api</code> to confirm</label>
<input class="cd-input" id="cdInput" type="text" placeholder="aurora-api" autocomplete="off" spellcheck="false" oninput="checkPhrase(this)">
<div class="cd-actions">
<button class="cd-cancel" onclick="closeDialog()">Cancel</button>
<button class="cd-confirm" id="cdConfirm" onclick="confirmDelete()" disabled>Delete project</button>
</div>
</div>
</div>
</div>
<script>
var overlay = document.getElementById('cdOverlay');
var input = document.getElementById('cdInput');
var confirm = document.getElementById('cdConfirm');
var PHRASE = document.getElementById('cdPhrase').textContent;
var busy = false;
function openDialog() {
overlay.classList.add('show');
setTimeout(function () { input.focus(); }, 120);
}
function closeDialog() {
if (busy) return;
overlay.classList.remove('show');
input.value = '';
input.classList.remove('match');
confirm.disabled = true;
confirm.classList.remove('done');
confirm.textContent = 'Delete project';
}
function overlayClick(e) {
if (e.target === overlay) closeDialog();
}
function checkPhrase(el) {
var match = el.value.trim() === PHRASE;
confirm.disabled = !match;
el.classList.toggle('match', match);
}
function confirmDelete() {
if (confirm.disabled || busy) return;
busy = true;
confirm.textContent = 'Deleting…';
setTimeout(function () {
confirm.textContent = '✓ Deleted';
confirm.classList.add('done');
setTimeout(function () { busy = false; closeDialog(); }, 1000);
}, 850);
}
document.addEventListener('keydown', function (e) {
if (e.key === 'Escape') closeDialog();
});
</script>
</body>
</html><!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Confirm Dialog</title>
<!-- Tailwind CSS v3+ — arbitrary value classes (e.g. bg-[#0f172a]) are valid -->
<script src="https://cdn.tailwindcss.com"></script>
<style>
* {
box-sizing:border-box;margin:0;padding:0
}
body {
font-family:system-ui,-apple-system,sans-serif;background:#f1f5f9;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:24px
}
.cd-overlay.show {
opacity:1;visibility:visible
}
.cd-overlay.show .cd-dialog {
transform:scale(1) translateY(0)
}
.cd-text strong {
color:#1e293b;font-weight:700
}
.cd-label code {
background:#f1f5f9;color:#dc2626;font-weight:700;padding:2px 6px;border-radius:5px;font-family:ui-monospace,monospace;font-size:12px
}
.cd-input.match {
border-color:#10b981
}
.cd-cancel,.cd-confirm {
flex:1;padding:11px;border:none;border-radius:10px;font-size:14px;font-weight:700;cursor:pointer;font-family:inherit;transition:background .15s,opacity .15s
}
.cd-confirm:hover:not(:disabled) {
background:#b91c1c
}
.cd-confirm:disabled {
background:#fca5a5;cursor:not-allowed;opacity:.7
}
.cd-confirm.done {
background:#10b981!important
}
</style>
</head>
<body>
<div class="cd-page">
<button class="inline-flex items-center gap-2 bg-[#fff] text-[#dc2626] border rounded-[11px] py-[11px] px-[18px] text-sm font-bold cursor-pointer font-[inherit] [transition:background_.15s,border-color_.15s] hover:bg-[#fef2f2] hover:border-[#fca5a5]" onclick="openDialog()">
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2m2 0v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/></svg>
Delete project
</button>
<div class="cd-overlay fixed inset-0 bg-[rgba(15,23,42,.5)] [backdrop-filter:blur(4px)] flex items-center justify-center p-5 opacity-0 invisible [transition:opacity_.25s,visibility_.25s] z-50" id="cdOverlay" onclick="overlayClick(event)">
<div class="cd-dialog bg-[#fff] rounded-[18px] p-[26px] w-full max-w-[380px] shadow-[0_25px_60px_rgba(0,0,0,.3)] [transform:scale(.94)_translateY(10px)] [transition:transform_.25s_cubic-bezier(.2,1.1,.4,1)]" role="alertdialog" aria-modal="true" aria-labelledby="cdTitle">
<div class="w-12 h-12 rounded-full bg-[#fee2e2] flex items-center justify-center mb-3.5">
<svg viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="#dc2626" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 9v4m0 4h.01M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0z"/></svg>
</div>
<h2 class="text-lg font-extrabold text-[#1e293b] mb-2" id="cdTitle">Delete this project?</h2>
<p class="cd-text text-[13px] text-[#64748b] leading-[1.55] mb-[18px]">This permanently deletes <strong>aurora-api</strong> and all of its deployments, logs, and secrets. This action <strong>cannot be undone</strong>.</p>
<label class="cd-label block text-xs text-[#475569] mb-[7px]">Type <code id="cdPhrase">aurora-api</code> to confirm</label>
<input class="cd-input w-full py-2.5 px-3 border rounded-[10px] text-sm text-[#1e293b] outline-none font-mono [transition:border-color_.15s,box-shadow_.15s] mb-[18px] focus:border-[#6366f1] focus:shadow-[0_0_0_3px_rgba(99,102,241,.12)]" id="cdInput" type="text" placeholder="aurora-api" autocomplete="off" spellcheck="false" oninput="checkPhrase(this)">
<div class="flex gap-2.5">
<button class="cd-cancel bg-[#f1f5f9] text-[#475569] hover:bg-[#e2e8f0]" onclick="closeDialog()">Cancel</button>
<button class="cd-confirm bg-[#dc2626] text-[#fff]" id="cdConfirm" onclick="confirmDelete()" disabled>Delete project</button>
</div>
</div>
</div>
</div>
<script>
var overlay = document.getElementById('cdOverlay');
var input = document.getElementById('cdInput');
var confirm = document.getElementById('cdConfirm');
var PHRASE = document.getElementById('cdPhrase').textContent;
var busy = false;
function openDialog() {
overlay.classList.add('show');
setTimeout(function () { input.focus(); }, 120);
}
function closeDialog() {
if (busy) return;
overlay.classList.remove('show');
input.value = '';
input.classList.remove('match');
confirm.disabled = true;
confirm.classList.remove('done');
confirm.textContent = 'Delete project';
}
function overlayClick(e) {
if (e.target === overlay) closeDialog();
}
function checkPhrase(el) {
var match = el.value.trim() === PHRASE;
confirm.disabled = !match;
el.classList.toggle('match', match);
}
function confirmDelete() {
if (confirm.disabled || busy) return;
busy = true;
confirm.textContent = 'Deleting…';
setTimeout(function () {
confirm.textContent = '✓ Deleted';
confirm.classList.add('done');
setTimeout(function () { busy = false; closeDialog(); }, 1000);
}, 850);
}
document.addEventListener('keydown', function (e) {
if (e.key === 'Escape') closeDialog();
});
</script>
</body>
</html>import React, { useEffect } from 'react';
// CSS — optionally move to ConfirmDialog.module.css
const css = `
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:system-ui,-apple-system,sans-serif;background:#f1f5f9;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:24px}
.cd-trigger{display:inline-flex;align-items:center;gap:8px;background:#fff;color:#dc2626;border:1.5px solid #fecaca;border-radius:11px;padding:11px 18px;font-size:14px;font-weight:700;cursor:pointer;font-family:inherit;transition:background .15s,border-color .15s}
.cd-trigger:hover{background:#fef2f2;border-color:#fca5a5}
.cd-overlay{position:fixed;inset:0;background:rgba(15,23,42,.5);backdrop-filter:blur(4px);display:flex;align-items:center;justify-content:center;padding:20px;opacity:0;visibility:hidden;transition:opacity .25s,visibility .25s;z-index:50}
.cd-overlay.show{opacity:1;visibility:visible}
.cd-dialog{background:#fff;border-radius:18px;padding:26px;width:100%;max-width:380px;box-shadow:0 25px 60px rgba(0,0,0,.3);transform:scale(.94) translateY(10px);transition:transform .25s cubic-bezier(.2,1.1,.4,1)}
.cd-overlay.show .cd-dialog{transform:scale(1) translateY(0)}
.cd-icon{width:48px;height:48px;border-radius:50%;background:#fee2e2;display:flex;align-items:center;justify-content:center;margin-bottom:14px}
.cd-title{font-size:18px;font-weight:800;color:#1e293b;margin-bottom:8px}
.cd-text{font-size:13px;color:#64748b;line-height:1.55;margin-bottom:18px}
.cd-text strong{color:#1e293b;font-weight:700}
.cd-label{display:block;font-size:12px;color:#475569;margin-bottom:7px}
.cd-label code{background:#f1f5f9;color:#dc2626;font-weight:700;padding:2px 6px;border-radius:5px;font-family:ui-monospace,monospace;font-size:12px}
.cd-input{width:100%;padding:10px 12px;border:1.5px solid #e2e8f0;border-radius:10px;font-size:14px;color:#1e293b;outline:none;font-family:ui-monospace,monospace;transition:border-color .15s,box-shadow .15s;margin-bottom:18px}
.cd-input:focus{border-color:#6366f1;box-shadow:0 0 0 3px rgba(99,102,241,.12)}
.cd-input.match{border-color:#10b981}
.cd-actions{display:flex;gap:10px}
.cd-cancel,.cd-confirm{flex:1;padding:11px;border:none;border-radius:10px;font-size:14px;font-weight:700;cursor:pointer;font-family:inherit;transition:background .15s,opacity .15s}
.cd-cancel{background:#f1f5f9;color:#475569}
.cd-cancel:hover{background:#e2e8f0}
.cd-confirm{background:#dc2626;color:#fff}
.cd-confirm:hover:not(:disabled){background:#b91c1c}
.cd-confirm:disabled{background:#fca5a5;cursor:not-allowed;opacity:.7}
.cd-confirm.done{background:#10b981!important}
`;
export default function ConfirmDialog() {
// Auto-generated escape hatch: the original snippet's vanilla JS runs once
// after mount and queries the rendered DOM. For idiomatic React, lift this
// into state + handlers.
useEffect(() => {
const _listeners = [];
const _originalAddEventListener = EventTarget.prototype.addEventListener;
EventTarget.prototype.addEventListener = function(type, listener, options) {
_listeners.push({ target: this, type, listener, options });
return _originalAddEventListener.call(this, type, listener, options);
};
var overlay = document.getElementById('cdOverlay');
var input = document.getElementById('cdInput');
var confirm = document.getElementById('cdConfirm');
var PHRASE = document.getElementById('cdPhrase').textContent;
var busy = false;
function openDialog() {
overlay.classList.add('show');
setTimeout(function () { input.focus(); }, 120);
}
function closeDialog() {
if (busy) return;
overlay.classList.remove('show');
input.value = '';
input.classList.remove('match');
confirm.disabled = true;
confirm.classList.remove('done');
confirm.textContent = 'Delete project';
}
function overlayClick(e) {
if (e.target === overlay) closeDialog();
}
function checkPhrase(el) {
var match = el.value.trim() === PHRASE;
confirm.disabled = !match;
el.classList.toggle('match', match);
}
function confirmDelete() {
if (confirm.disabled || busy) return;
busy = true;
confirm.textContent = 'Deleting…';
setTimeout(function () {
confirm.textContent = '✓ Deleted';
confirm.classList.add('done');
setTimeout(function () { busy = false; closeDialog(); }, 1000);
}, 850);
}
document.addEventListener('keydown', function (e) {
if (e.key === 'Escape') closeDialog();
});
// Cleanup: restore addEventListener and remove all listeners
return () => {
EventTarget.prototype.addEventListener = _originalAddEventListener;
for (const { target, type, listener, options } of _listeners) {
target.removeEventListener(type, listener, options);
}
};
// Expose handlers used by inline JSX events to the global scope
if (typeof openDialog === 'function') window.openDialog = openDialog;
if (typeof overlayClick === 'function') window.overlayClick = overlayClick;
if (typeof checkPhrase === 'function') window.checkPhrase = checkPhrase;
if (typeof closeDialog === 'function') window.closeDialog = closeDialog;
if (typeof confirmDelete === 'function') window.confirmDelete = confirmDelete;
}, []);
return (
<>
<style>{css}</style>
<div className="cd-page">
<button className="cd-trigger" onClick={(event) => { openDialog() }}>
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" stroke-linejoin="round"><path d="M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2m2 0v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/></svg>
Delete project
</button>
<div className="cd-overlay" id="cdOverlay" onClick={(event) => { overlayClick(event) }}>
<div className="cd-dialog" role="alertdialog" aria-modal="true" aria-labelledby="cdTitle">
<div className="cd-icon">
<svg viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="#dc2626" strokeWidth="2" strokeLinecap="round" stroke-linejoin="round"><path d="M12 9v4m0 4h.01M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0z"/></svg>
</div>
<h2 className="cd-title" id="cdTitle">Delete this project?</h2>
<p className="cd-text">This permanently deletes <strong>aurora-api</strong> and all of its deployments, logs, and secrets. This action <strong>cannot be undone</strong>.</p>
<label className="cd-label">Type <code id="cdPhrase">aurora-api</code> to confirm</label>
<input className="cd-input" id="cdInput" type="text" placeholder="aurora-api" autocomplete="off" spellcheck="false" onInput={(event) => { checkPhrase(event.currentTarget) }} />
<div className="cd-actions">
<button className="cd-cancel" onClick={(event) => { closeDialog() }}>Cancel</button>
<button className="cd-confirm" id="cdConfirm" onClick={(event) => { confirmDelete() }} defaultDisabled>Delete project</button>
</div>
</div>
</div>
</div>
</>
);
}import React, { useEffect } from 'react';
// Requires Tailwind CSS v3+ — https://tailwindcss.com/docs/installation
// Arbitrary value classes (e.g. bg-[#0f172a]) are valid Tailwind v3+
export default function ConfirmDialog() {
// Auto-generated escape hatch: the original snippet's vanilla JS runs once
// after mount and queries the rendered DOM. For idiomatic React, lift this
// into state + handlers.
useEffect(() => {
const _listeners = [];
const _originalAddEventListener = EventTarget.prototype.addEventListener;
EventTarget.prototype.addEventListener = function(type, listener, options) {
_listeners.push({ target: this, type, listener, options });
return _originalAddEventListener.call(this, type, listener, options);
};
var overlay = document.getElementById('cdOverlay');
var input = document.getElementById('cdInput');
var confirm = document.getElementById('cdConfirm');
var PHRASE = document.getElementById('cdPhrase').textContent;
var busy = false;
function openDialog() {
overlay.classList.add('show');
setTimeout(function () { input.focus(); }, 120);
}
function closeDialog() {
if (busy) return;
overlay.classList.remove('show');
input.value = '';
input.classList.remove('match');
confirm.disabled = true;
confirm.classList.remove('done');
confirm.textContent = 'Delete project';
}
function overlayClick(e) {
if (e.target === overlay) closeDialog();
}
function checkPhrase(el) {
var match = el.value.trim() === PHRASE;
confirm.disabled = !match;
el.classList.toggle('match', match);
}
function confirmDelete() {
if (confirm.disabled || busy) return;
busy = true;
confirm.textContent = 'Deleting…';
setTimeout(function () {
confirm.textContent = '✓ Deleted';
confirm.classList.add('done');
setTimeout(function () { busy = false; closeDialog(); }, 1000);
}, 850);
}
document.addEventListener('keydown', function (e) {
if (e.key === 'Escape') closeDialog();
});
// Cleanup: restore addEventListener and remove all listeners
return () => {
EventTarget.prototype.addEventListener = _originalAddEventListener;
for (const { target, type, listener, options } of _listeners) {
target.removeEventListener(type, listener, options);
}
};
// Expose handlers used by inline JSX events to the global scope
if (typeof openDialog === 'function') window.openDialog = openDialog;
if (typeof overlayClick === 'function') window.overlayClick = overlayClick;
if (typeof checkPhrase === 'function') window.checkPhrase = checkPhrase;
if (typeof closeDialog === 'function') window.closeDialog = closeDialog;
if (typeof confirmDelete === 'function') window.confirmDelete = confirmDelete;
}, []);
return (
<>
<style>{`
* {
box-sizing:border-box;margin:0;padding:0
}
body {
font-family:system-ui,-apple-system,sans-serif;background:#f1f5f9;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:24px
}
.cd-overlay.show {
opacity:1;visibility:visible
}
.cd-overlay.show .cd-dialog {
transform:scale(1) translateY(0)
}
.cd-text strong {
color:#1e293b;font-weight:700
}
.cd-label code {
background:#f1f5f9;color:#dc2626;font-weight:700;padding:2px 6px;border-radius:5px;font-family:ui-monospace,monospace;font-size:12px
}
.cd-input.match {
border-color:#10b981
}
.cd-cancel,.cd-confirm {
flex:1;padding:11px;border:none;border-radius:10px;font-size:14px;font-weight:700;cursor:pointer;font-family:inherit;transition:background .15s,opacity .15s
}
.cd-confirm:hover:not(:disabled) {
background:#b91c1c
}
.cd-confirm:disabled {
background:#fca5a5;cursor:not-allowed;opacity:.7
}
.cd-confirm.done {
background:#10b981!important
}
`}</style>
<div className="cd-page">
<button className="inline-flex items-center gap-2 bg-[#fff] text-[#dc2626] border rounded-[11px] py-[11px] px-[18px] text-sm font-bold cursor-pointer font-[inherit] [transition:background_.15s,border-color_.15s] hover:bg-[#fef2f2] hover:border-[#fca5a5]" onClick={(event) => { openDialog() }}>
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" stroke-linejoin="round"><path d="M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2m2 0v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/></svg>
Delete project
</button>
<div className="cd-overlay fixed inset-0 bg-[rgba(15,23,42,.5)] [backdrop-filter:blur(4px)] flex items-center justify-center p-5 opacity-0 invisible [transition:opacity_.25s,visibility_.25s] z-50" id="cdOverlay" onClick={(event) => { overlayClick(event) }}>
<div className="cd-dialog bg-[#fff] rounded-[18px] p-[26px] w-full max-w-[380px] shadow-[0_25px_60px_rgba(0,0,0,.3)] [transform:scale(.94)_translateY(10px)] [transition:transform_.25s_cubic-bezier(.2,1.1,.4,1)]" role="alertdialog" aria-modal="true" aria-labelledby="cdTitle">
<div className="w-12 h-12 rounded-full bg-[#fee2e2] flex items-center justify-center mb-3.5">
<svg viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="#dc2626" strokeWidth="2" strokeLinecap="round" stroke-linejoin="round"><path d="M12 9v4m0 4h.01M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0z"/></svg>
</div>
<h2 className="text-lg font-extrabold text-[#1e293b] mb-2" id="cdTitle">Delete this project?</h2>
<p className="cd-text text-[13px] text-[#64748b] leading-[1.55] mb-[18px]">This permanently deletes <strong>aurora-api</strong> and all of its deployments, logs, and secrets. This action <strong>cannot be undone</strong>.</p>
<label className="cd-label block text-xs text-[#475569] mb-[7px]">Type <code id="cdPhrase">aurora-api</code> to confirm</label>
<input className="cd-input w-full py-2.5 px-3 border rounded-[10px] text-sm text-[#1e293b] outline-none font-mono [transition:border-color_.15s,box-shadow_.15s] mb-[18px] focus:border-[#6366f1] focus:shadow-[0_0_0_3px_rgba(99,102,241,.12)]" id="cdInput" type="text" placeholder="aurora-api" autocomplete="off" spellcheck="false" onInput={(event) => { checkPhrase(event.currentTarget) }} />
<div className="flex gap-2.5">
<button className="cd-cancel bg-[#f1f5f9] text-[#475569] hover:bg-[#e2e8f0]" onClick={(event) => { closeDialog() }}>Cancel</button>
<button className="cd-confirm bg-[#dc2626] text-[#fff]" id="cdConfirm" onClick={(event) => { confirmDelete() }} defaultDisabled>Delete project</button>
</div>
</div>
</div>
</div>
</>
);
}<template>
<div class="cd-page">
<button class="cd-trigger" @click="openDialog()">
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2m2 0v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/></svg>
Delete project
</button>
<div class="cd-overlay" id="cdOverlay" @click="overlayClick($event)">
<div class="cd-dialog" role="alertdialog" aria-modal="true" aria-labelledby="cdTitle">
<div class="cd-icon">
<svg viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="#dc2626" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 9v4m0 4h.01M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0z"/></svg>
</div>
<h2 class="cd-title" id="cdTitle">Delete this project?</h2>
<p class="cd-text">This permanently deletes <strong>aurora-api</strong> and all of its deployments, logs, and secrets. This action <strong>cannot be undone</strong>.</p>
<label class="cd-label">Type <code id="cdPhrase">aurora-api</code> to confirm</label>
<input class="cd-input" id="cdInput" type="text" placeholder="aurora-api" autocomplete="off" spellcheck="false" @input="checkPhrase($event.currentTarget)">
<div class="cd-actions">
<button class="cd-cancel" @click="closeDialog()">Cancel</button>
<button class="cd-confirm" id="cdConfirm" @click="confirmDelete()" disabled>Delete project</button>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { onMounted } from 'vue';
let overlay;
let input;
let confirm;
let PHRASE;
var busy = false;
function openDialog() {
overlay.classList.add('show');
setTimeout(function () { input.focus(); }, 120);
}
function closeDialog() {
if (busy) return;
overlay.classList.remove('show');
input.value = '';
input.classList.remove('match');
confirm.disabled = true;
confirm.classList.remove('done');
confirm.textContent = 'Delete project';
}
function overlayClick(e) {
if (e.target === overlay) closeDialog();
}
function checkPhrase(el) {
var match = el.value.trim() === PHRASE;
confirm.disabled = !match;
el.classList.toggle('match', match);
}
function confirmDelete() {
if (confirm.disabled || busy) return;
busy = true;
confirm.textContent = 'Deleting…';
setTimeout(function () {
confirm.textContent = '✓ Deleted';
confirm.classList.add('done');
setTimeout(function () { busy = false; closeDialog(); }, 1000);
}, 850);
}
onMounted(() => {
overlay = document.getElementById('cdOverlay');
input = document.getElementById('cdInput');
confirm = document.getElementById('cdConfirm');
PHRASE = document.getElementById('cdPhrase').textContent;
document.addEventListener('keydown', function (e) {
if (e.key === 'Escape') closeDialog();
});
});
</script>
<style scoped>
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:system-ui,-apple-system,sans-serif;background:#f1f5f9;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:24px}
.cd-trigger{display:inline-flex;align-items:center;gap:8px;background:#fff;color:#dc2626;border:1.5px solid #fecaca;border-radius:11px;padding:11px 18px;font-size:14px;font-weight:700;cursor:pointer;font-family:inherit;transition:background .15s,border-color .15s}
.cd-trigger:hover{background:#fef2f2;border-color:#fca5a5}
.cd-overlay{position:fixed;inset:0;background:rgba(15,23,42,.5);backdrop-filter:blur(4px);display:flex;align-items:center;justify-content:center;padding:20px;opacity:0;visibility:hidden;transition:opacity .25s,visibility .25s;z-index:50}
.cd-overlay.show{opacity:1;visibility:visible}
.cd-dialog{background:#fff;border-radius:18px;padding:26px;width:100%;max-width:380px;box-shadow:0 25px 60px rgba(0,0,0,.3);transform:scale(.94) translateY(10px);transition:transform .25s cubic-bezier(.2,1.1,.4,1)}
.cd-overlay.show .cd-dialog{transform:scale(1) translateY(0)}
.cd-icon{width:48px;height:48px;border-radius:50%;background:#fee2e2;display:flex;align-items:center;justify-content:center;margin-bottom:14px}
.cd-title{font-size:18px;font-weight:800;color:#1e293b;margin-bottom:8px}
.cd-text{font-size:13px;color:#64748b;line-height:1.55;margin-bottom:18px}
.cd-text strong{color:#1e293b;font-weight:700}
.cd-label{display:block;font-size:12px;color:#475569;margin-bottom:7px}
.cd-label code{background:#f1f5f9;color:#dc2626;font-weight:700;padding:2px 6px;border-radius:5px;font-family:ui-monospace,monospace;font-size:12px}
.cd-input{width:100%;padding:10px 12px;border:1.5px solid #e2e8f0;border-radius:10px;font-size:14px;color:#1e293b;outline:none;font-family:ui-monospace,monospace;transition:border-color .15s,box-shadow .15s;margin-bottom:18px}
.cd-input:focus{border-color:#6366f1;box-shadow:0 0 0 3px rgba(99,102,241,.12)}
.cd-input.match{border-color:#10b981}
.cd-actions{display:flex;gap:10px}
.cd-cancel,.cd-confirm{flex:1;padding:11px;border:none;border-radius:10px;font-size:14px;font-weight:700;cursor:pointer;font-family:inherit;transition:background .15s,opacity .15s}
.cd-cancel{background:#f1f5f9;color:#475569}
.cd-cancel:hover{background:#e2e8f0}
.cd-confirm{background:#dc2626;color:#fff}
.cd-confirm:hover:not(:disabled){background:#b91c1c}
.cd-confirm:disabled{background:#fca5a5;cursor:not-allowed;opacity:.7}
.cd-confirm.done{background:#10b981!important}
</style>// @ts-nocheck
// Note: vanilla JS DOM manipulation is preserved as-is inside ngAfterViewInit().
// For idiomatic Angular, replace document.getElementById() with @ViewChild() refs
// and move state into component properties with two-way binding.
import { Component, AfterViewInit, ViewEncapsulation } from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
selector: 'app-confirm-dialog',
standalone: true,
imports: [CommonModule],
encapsulation: ViewEncapsulation.None,
template: `
<div class="cd-page">
<button class="cd-trigger" (click)="openDialog()">
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2m2 0v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/></svg>
Delete project
</button>
<div class="cd-overlay" id="cdOverlay" (click)="overlayClick($event)">
<div class="cd-dialog" role="alertdialog" aria-modal="true" aria-labelledby="cdTitle">
<div class="cd-icon">
<svg viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="#dc2626" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 9v4m0 4h.01M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0z"/></svg>
</div>
<h2 class="cd-title" id="cdTitle">Delete this project?</h2>
<p class="cd-text">This permanently deletes <strong>aurora-api</strong> and all of its deployments, logs, and secrets. This action <strong>cannot be undone</strong>.</p>
<label class="cd-label">Type <code id="cdPhrase">aurora-api</code> to confirm</label>
<input class="cd-input" id="cdInput" type="text" placeholder="aurora-api" autocomplete="off" spellcheck="false" (input)="checkPhrase($event.currentTarget)">
<div class="cd-actions">
<button class="cd-cancel" (click)="closeDialog()">Cancel</button>
<button class="cd-confirm" id="cdConfirm" (click)="confirmDelete()" disabled>Delete project</button>
</div>
</div>
</div>
</div>
`,
styles: [`
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:system-ui,-apple-system,sans-serif;background:#f1f5f9;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:24px}
.cd-trigger{display:inline-flex;align-items:center;gap:8px;background:#fff;color:#dc2626;border:1.5px solid #fecaca;border-radius:11px;padding:11px 18px;font-size:14px;font-weight:700;cursor:pointer;font-family:inherit;transition:background .15s,border-color .15s}
.cd-trigger:hover{background:#fef2f2;border-color:#fca5a5}
.cd-overlay{position:fixed;inset:0;background:rgba(15,23,42,.5);backdrop-filter:blur(4px);display:flex;align-items:center;justify-content:center;padding:20px;opacity:0;visibility:hidden;transition:opacity .25s,visibility .25s;z-index:50}
.cd-overlay.show{opacity:1;visibility:visible}
.cd-dialog{background:#fff;border-radius:18px;padding:26px;width:100%;max-width:380px;box-shadow:0 25px 60px rgba(0,0,0,.3);transform:scale(.94) translateY(10px);transition:transform .25s cubic-bezier(.2,1.1,.4,1)}
.cd-overlay.show .cd-dialog{transform:scale(1) translateY(0)}
.cd-icon{width:48px;height:48px;border-radius:50%;background:#fee2e2;display:flex;align-items:center;justify-content:center;margin-bottom:14px}
.cd-title{font-size:18px;font-weight:800;color:#1e293b;margin-bottom:8px}
.cd-text{font-size:13px;color:#64748b;line-height:1.55;margin-bottom:18px}
.cd-text strong{color:#1e293b;font-weight:700}
.cd-label{display:block;font-size:12px;color:#475569;margin-bottom:7px}
.cd-label code{background:#f1f5f9;color:#dc2626;font-weight:700;padding:2px 6px;border-radius:5px;font-family:ui-monospace,monospace;font-size:12px}
.cd-input{width:100%;padding:10px 12px;border:1.5px solid #e2e8f0;border-radius:10px;font-size:14px;color:#1e293b;outline:none;font-family:ui-monospace,monospace;transition:border-color .15s,box-shadow .15s;margin-bottom:18px}
.cd-input:focus{border-color:#6366f1;box-shadow:0 0 0 3px rgba(99,102,241,.12)}
.cd-input.match{border-color:#10b981}
.cd-actions{display:flex;gap:10px}
.cd-cancel,.cd-confirm{flex:1;padding:11px;border:none;border-radius:10px;font-size:14px;font-weight:700;cursor:pointer;font-family:inherit;transition:background .15s,opacity .15s}
.cd-cancel{background:#f1f5f9;color:#475569}
.cd-cancel:hover{background:#e2e8f0}
.cd-confirm{background:#dc2626;color:#fff}
.cd-confirm:hover:not(:disabled){background:#b91c1c}
.cd-confirm:disabled{background:#fca5a5;cursor:not-allowed;opacity:.7}
.cd-confirm.done{background:#10b981!important}
`]
})
export class ConfirmDialogComponent implements AfterViewInit {
ngAfterViewInit(): void {
var overlay = document.getElementById('cdOverlay');
var input = document.getElementById('cdInput');
var confirm = document.getElementById('cdConfirm');
var PHRASE = document.getElementById('cdPhrase').textContent;
var busy = false;
function openDialog() {
overlay.classList.add('show');
setTimeout(function () { input.focus(); }, 120);
}
function closeDialog() {
if (busy) return;
overlay.classList.remove('show');
input.value = '';
input.classList.remove('match');
confirm.disabled = true;
confirm.classList.remove('done');
confirm.textContent = 'Delete project';
}
function overlayClick(e) {
if (e.target === overlay) closeDialog();
}
function checkPhrase(el) {
var match = el.value.trim() === PHRASE;
confirm.disabled = !match;
el.classList.toggle('match', match);
}
function confirmDelete() {
if (confirm.disabled || busy) return;
busy = true;
confirm.textContent = 'Deleting…';
setTimeout(function () {
confirm.textContent = '✓ Deleted';
confirm.classList.add('done');
setTimeout(function () { busy = false; closeDialog(); }, 1000);
}, 850);
}
document.addEventListener('keydown', function (e) {
if (e.key === 'Escape') closeDialog();
});
// Bind inline-handler functions to the component so the template can call them
Object.assign(this, { openDialog, closeDialog, overlayClick, checkPhrase, confirmDelete });
}
}