diff --git a/templates/repo/issue/list.tmpl b/templates/repo/issue/list.tmpl
index 5c9a5937a..12eb31acd 100644
--- a/templates/repo/issue/list.tmpl
+++ b/templates/repo/issue/list.tmpl
@@ -282,9 +282,15 @@
 					{{if not .Repository.IsArchived}}
 					
 					{{if .IsShowClosed}}
-						
diff --git a/web_src/js/features/common-global.js b/web_src/js/features/common-global.js
index b6e1790a9..5e418fa48 100644
--- a/web_src/js/features/common-global.js
+++ b/web_src/js/features/common-global.js
@@ -8,6 +8,7 @@ import {svg} from '../svg.js';
 import {hideElem, showElem, toggleElem} from '../utils/dom.js';
 import {htmlEscape} from 'escape-goat';
 import {createTippy} from '../modules/tippy.js';
+import {confirmModal} from './comp/ConfirmModal.js';
 
 const {appUrl, appSubUrl, csrfToken, i18n} = window.config;
 
@@ -264,7 +265,7 @@ export function initGlobalDropzone() {
   }
 }
 
-function linkAction(e) {
+async function linkAction(e) {
   e.preventDefault();
 
   // A "link-action" can post AJAX request to its "data-url"
@@ -291,33 +292,16 @@ function linkAction(e) {
     });
   };
 
-  const modalConfirmHtml = htmlEscape($this.attr('data-modal-confirm') || '');
-  if (!modalConfirmHtml) {
+  const modalConfirmContent = htmlEscape($this.attr('data-modal-confirm') || '');
+  if (!modalConfirmContent) {
     doRequest();
     return;
   }
 
-  const okButtonColor = $this.hasClass('red') || $this.hasClass('yellow') || $this.hasClass('orange') || $this.hasClass('negative') ? 'orange' : 'green';
-
-  const $modal = $(`
-
-  
${modalConfirmHtml}
-  
-    
-    
-  
-
-`);
-
-  $modal.appendTo(document.body);
-  $modal.modal({
-    onApprove() {
-      doRequest();
-    },
-    onHidden() {
-      $modal.remove();
-    },
-  }).modal('show');
+  const isRisky = $this.hasClass('red') || $this.hasClass('yellow') || $this.hasClass('orange') || $this.hasClass('negative');
+  if (await confirmModal({content: modalConfirmContent, buttonColor: isRisky ? 'orange' : 'green'})) {
+    doRequest();
+  }
 }
 
 export function initGlobalLinkActions() {
diff --git a/web_src/js/features/comp/ConfirmModal.js b/web_src/js/features/comp/ConfirmModal.js
new file mode 100644
index 000000000..1edcfd952
--- /dev/null
+++ b/web_src/js/features/comp/ConfirmModal.js
@@ -0,0 +1,30 @@
+import $ from 'jquery';
+import {svg} from '../../svg.js';
+import {htmlEscape} from 'escape-goat';
+
+const {i18n} = window.config;
+
+export async function confirmModal(opts = {content: '', buttonColor: 'green'}) {
+  return new Promise((resolve) => {
+    const $modal = $(`
+
+  
${htmlEscape(opts.content)}
+  
+    
+    
+  
+
+`);
+
+    $modal.appendTo(document.body);
+    $modal.modal({
+      onApprove() {
+        resolve(true);
+      },
+      onHidden() {
+        $modal.remove();
+        resolve(false);
+      },
+    }).modal('show');
+  });
+}
diff --git a/web_src/js/features/repo-issue-list.js b/web_src/js/features/repo-issue-list.js
index cc50ec5f8..4d61de0ce 100644
--- a/web_src/js/features/repo-issue-list.js
+++ b/web_src/js/features/repo-issue-list.js
@@ -3,6 +3,7 @@ import {updateIssuesMeta} from './repo-issue.js';
 import {toggleElem} from '../utils/dom.js';
 import {htmlEscape} from 'escape-goat';
 import {Sortable} from 'sortablejs';
+import {confirmModal} from './comp/ConfirmModal.js';
 
 function initRepoIssueListCheckboxes() {
   const $issueSelectAll = $('.issue-checkbox-all');
@@ -36,19 +37,36 @@ function initRepoIssueListCheckboxes() {
 
   $('.issue-action').on('click', async function (e) {
     e.preventDefault();
+
+    const url = this.getAttribute('data-url');
     let action = this.getAttribute('data-action');
     let elementId = this.getAttribute('data-element-id');
-    const url = this.getAttribute('data-url');
-    const issueIDs = $('.issue-checkbox:checked').map((_, el) => {
-      return el.getAttribute('data-issue-id');
-    }).get().join(',');
-    if (elementId === '0' && url.slice(-9) === '/assignee') {
+    let issueIDs = [];
+    for (const el of document.querySelectorAll('.issue-checkbox:checked')) {
+      issueIDs.push(el.getAttribute('data-issue-id'));
+    }
+    issueIDs = issueIDs.join(',');
+    if (!issueIDs) return;
+
+    // for assignee
+    if (elementId === '0' && url.endsWith('/assignee')) {
       elementId = '';
       action = 'clear';
     }
+
+    // for toggle
     if (action === 'toggle' && e.altKey) {
       action = 'toggle-alt';
     }
+
+    // for delete
+    if (action === 'delete') {
+      const confirmText = e.target.getAttribute('data-action-delete-confirm');
+      if (!await confirmModal({content: confirmText, buttonColor: 'orange'})) {
+        return;
+      }
+    }
+
     updateIssuesMeta(
       url,
       action,
diff --git a/web_src/js/modules/tippy.js b/web_src/js/modules/tippy.js
index 3409e1c71..372f7bc8f 100644
--- a/web_src/js/modules/tippy.js
+++ b/web_src/js/modules/tippy.js
@@ -3,11 +3,9 @@ import tippy from 'tippy.js';
 const visibleInstances = new Set();
 
 export function createTippy(target, opts = {}) {
-  const {role, content, onHide: optsOnHide, onDestroy: optsOnDestroy, onShow: optOnShow} = opts;
-  delete opts.onHide;
-  delete opts.onDestroy;
-  delete opts.onShow;
-
+  // the callback functions should be destructured from opts,
+  // because we should use our own wrapper functions to handle them, do not let the user override them
+  const {onHide, onShow, onDestroy, ...other} = opts;
   const instance = tippy(target, {
     appendTo: document.body,
     animation: false,
@@ -18,11 +16,11 @@ export function createTippy(target, opts = {}) {
     maxWidth: 500, // increase over default 350px
     onHide: (instance) => {
       visibleInstances.delete(instance);
-      return optsOnHide?.(instance);
+      return onHide?.(instance);
     },
     onDestroy: (instance) => {
       visibleInstances.delete(instance);
-      return optsOnDestroy?.(instance);
+      return onDestroy?.(instance);
     },
     onShow: (instance) => {
       // hide other tooltip instances so only one tooltip shows at a time
@@ -32,19 +30,19 @@ export function createTippy(target, opts = {}) {
         }
       }
       visibleInstances.add(instance);
-      return optOnShow?.(instance);
+      return onShow?.(instance);
     },
     arrow: `
`,
     role: 'menu', // HTML role attribute, only tooltips should use "tooltip"
-    theme: role || 'menu', // CSS theme, we support either "tooltip" or "menu"
-    ...opts,
+    theme: other.role || 'menu', // CSS theme, we support either "tooltip" or "menu"
+    ...other,
   });
 
   // for popups where content refers to a DOM element, we use the 'tippy-target' class
   // to initially hide the content, now we can remove it as the content has been removed
   // from the DOM by tippy
-  if (content instanceof Element) {
-    content.classList.remove('tippy-target');
+  if (other.content instanceof Element) {
+    other.content.classList.remove('tippy-target');
   }
 
   return instance;