diff --git a/CHANGELOG.txt b/CHANGELOG.txt
index 7984cc2b23..4fd1a8d35e 100644
--- a/CHANGELOG.txt
+++ b/CHANGELOG.txt
@@ -31,6 +31,7 @@ Changelog
  * Maintenance: Allow `ViewSet` subclasses to customise `url_prefix` and `url_namespace` logic (Matt Westcott)
  * Maintenance: Simplify `SnippetViewSet` registration code (Sage Abdullah)
  * Maintenance: Rename groups `IndexView.results_template_name` to `results.html` (Sage Abdullah)
+ * Maintenance: Migrate form submission listing checkbox toggling to the shared `w-bulk` Stimulus implementation (LB (Ben) Johnston)
 
 
 5.1.1 (14.08.2023)
diff --git a/client/src/controllers/BulkController.test.js b/client/src/controllers/BulkController.test.js
index df956401c2..435e793374 100644
--- a/client/src/controllers/BulkController.test.js
+++ b/client/src/controllers/BulkController.test.js
@@ -4,7 +4,7 @@ import { BulkController } from './BulkController';
 describe('BulkController', () => {
   beforeEach(() => {
     document.body.innerHTML = `
-    <div data-controller="w-bulk">
+    <div id="bulk-container" data-controller="w-bulk">
       <input id="select-all" type="checkbox" data-w-bulk-target="all" data-action="w-bulk#toggleAll">
       <div id="checkboxes">
         <input type="checkbox" data-w-bulk-target="item" disabled data-action="w-bulk#toggle">
@@ -115,4 +115,41 @@ describe('BulkController', () => {
       expect(itemCheckbox.checked).toBe(true);
     });
   });
+
+  it('should allow for action targets to have classes toggled when any checkboxes are clicked', async () => {
+    const container = document.getElementById('bulk-container');
+
+    // create innerActions container that will be conditionally hidden with test classes
+    container.setAttribute(
+      'data-w-bulk-action-inactive-class',
+      'hidden w-invisible',
+    );
+    const innerActions = document.createElement('div');
+    innerActions.id = 'inner-actions';
+    innerActions.className = 'keep-me hidden w-invisible';
+    innerActions.setAttribute('data-w-bulk-target', 'action');
+    container.prepend(innerActions);
+
+    const innerActionsElement = document.getElementById('inner-actions');
+
+    expect(
+      document
+        .getElementById('checkboxes')
+        .querySelectorAll(':checked:not(:disabled)').length,
+    ).toEqual(0);
+
+    expect(innerActionsElement.className).toEqual('keep-me hidden w-invisible');
+
+    const firstCheckbox = document
+      .getElementById('checkboxes')
+      .querySelector("[type='checkbox']:not([disabled])");
+
+    firstCheckbox.click();
+
+    expect(innerActionsElement.className).toEqual('keep-me');
+
+    firstCheckbox.click();
+
+    expect(innerActionsElement.className).toEqual('keep-me hidden w-invisible');
+  });
 });
diff --git a/client/src/controllers/BulkController.ts b/client/src/controllers/BulkController.ts
index 2f29eab3c6..398f79a6ca 100644
--- a/client/src/controllers/BulkController.ts
+++ b/client/src/controllers/BulkController.ts
@@ -1,21 +1,40 @@
 import { Controller } from '@hotwired/stimulus';
+
 /**
  * Adds the ability to collectively toggle a set of (non-disabled) checkboxes.
  *
- * @example
+ * @example - Basic usage
  * <div data-controller="w-bulk">
  *   <input type="checkbox" data-action="w-bulk#toggleAll" data-w-bulk-target="all">
  *   <div>
- *     <input type="checkbox" data-action="w-bulk#change" data-w-bulk-target="item" disabled>
- *     <input type="checkbox" data-action="w-bulk#change" data-w-bulk-target="item">
- *     <input type="checkbox" data-action="w-bulk#change" data-w-bulk-target="item">
+ *     <input type="checkbox" data-action="w-bulk#toggle" data-w-bulk-target="item" disabled>
+ *     <input type="checkbox" data-action="w-bulk#toggle" data-w-bulk-target="item">
+ *     <input type="checkbox" data-action="w-bulk#toggle" data-w-bulk-target="item">
  *   </div>
  *   <button data-action="w-bulk#toggleAll" data-w-bulk-force-param="false">Clear all</button>
  *   <button data-action="w-bulk#toggleAll" data-w-bulk-force-param="true">Select all</button>
  * </div>
+ *
+ * @example - Showing and hiding an actions container
+ * <div data-controller="w-bulk" data-w-bulk-action-inactive-class="w-invisible">
+ *   <div class="w-invisible" data-w-bulk-target="action" id="inner-actions">
+ *     <button type="button">Some action</button>
+ *   </div>
+ *   <input data-action="w-bulk#toggleAll" data-w-bulk-target="all" type="checkbox"/>
+ *   <div id="checkboxes">
+ *     <input data-action="w-bulk#toggle" data-w-bulk-target="item" disabled="" type="checkbox" />
+ *     <input data-action="w-bulk#toggle" data-w-bulk-target="item" type="checkbox"/>
+ *     <input data-action="w-bulk#toggle" data-w-bulk-target="item" type="checkbox" />
+ *   </div>
+ * </div>
+
  */
 export class BulkController extends Controller<HTMLElement> {
-  static targets = ['all', 'item'];
+  static classes = ['actionInactive'];
+  static targets = ['action', 'all', 'item'];
+
+  /** Target(s) that will have the `actionInactive` classes removed if any actions are checked */
+  declare readonly actionTargets: HTMLElement[];
 
   /** All select-all checkbox targets */
   declare readonly allTargets: HTMLInputElement[];
@@ -23,6 +42,9 @@ export class BulkController extends Controller<HTMLElement> {
   /** All item checkbox targets */
   declare readonly itemTargets: HTMLInputElement[];
 
+  /** Classes to remove on the actions target if any actions are checked */
+  declare readonly actionInactiveClasses: string[];
+
   get activeItems() {
     return this.itemTargets.filter(({ disabled }) => !disabled);
   }
@@ -36,13 +58,27 @@ export class BulkController extends Controller<HTMLElement> {
 
   /**
    * When something is toggled, ensure the select all targets are kept in sync.
+   * Update the classes on the action targets to reflect the current state.
    */
   toggle() {
-    const isAllChecked = !this.activeItems.some((item) => !item.checked);
+    const activeItems = this.activeItems;
+    const totalCheckedItems = activeItems.filter((item) => item.checked).length;
+    const isAnyChecked = totalCheckedItems > 0;
+    const isAllChecked = totalCheckedItems === activeItems.length;
+
     this.allTargets.forEach((target) => {
       // eslint-disable-next-line no-param-reassign
       target.checked = isAllChecked;
     });
+
+    const actionInactiveClasses = this.actionInactiveClasses;
+    if (!actionInactiveClasses.length) return;
+
+    this.actionTargets.forEach((element) => {
+      actionInactiveClasses.forEach((actionInactiveClass) => {
+        element.classList.toggle(actionInactiveClass, !isAnyChecked);
+      });
+    });
   }
 
   /**
diff --git a/docs/releases/5.2.md b/docs/releases/5.2.md
index fd7518c476..5827f239da 100644
--- a/docs/releases/5.2.md
+++ b/docs/releases/5.2.md
@@ -50,6 +50,7 @@ depth: 1
  * Allow `ViewSet` subclasses to customise `url_prefix` and `url_namespace` logic (Matt Westcott)
  * Simplify `SnippetViewSet` registration code (Sage Abdullah)
  * Rename groups `IndexView.results_template_name` to `results.html` (Sage Abdullah)
+ * Migrate form submission listing checkbox toggling to the shared `w-bulk` Stimulus implementation (LB (Ben) Johnston)
 
 
 ## Upgrade considerations - changes affecting all projects
diff --git a/wagtail/contrib/forms/templates/wagtailforms/list_submissions.html b/wagtail/contrib/forms/templates/wagtailforms/list_submissions.html
index 8ddcfca0e3..28d3e30a61 100644
--- a/wagtail/contrib/forms/templates/wagtailforms/list_submissions.html
+++ b/wagtail/contrib/forms/templates/wagtailforms/list_submissions.html
@@ -1,17 +1,17 @@
 {% load i18n %}
 <div class="overflow">
-    <table class="listing">
+    <table class="listing" data-controller="w-bulk" data-w-bulk-action-inactive-class="w-invisible">
         <col />
         <col />
         <col />
         <thead>
             <tr>
                 <th colspan="{{ data_headings|length|add:1 }}">
-                    <button class="button no" id="delete-submissions" style="visibility: hidden">{% trans "Delete selected submissions" %}</button>
+                    <button class="button no w-invisible" data-w-bulk-target="action">{% trans "Delete selected submissions" %}</button>
                 </th>
             </tr>
             <tr>
-                <th><input type="checkbox" id="select-all" /></th>
+                <th><input type="checkbox" data-action="w-bulk#toggleAll" data-w-bulk-target="all" /></th>
                 {% for heading in data_headings %}
                     <th id="{{ heading.name }}" class="{% if heading.order %}ordered icon {% if heading.order == 'ascending' %}icon-arrow-up-after{% else %}icon-arrow-down-after{% endif %}{% endif %}">
                         {% if heading.order %}<a href="?order_by={% if heading.order == 'ascending' %}-{% endif %}{{ heading.name }}">{{ heading.label }}</a>{% else %}{{ heading.label }}{% endif %}
@@ -23,7 +23,7 @@
             {% for row in data_rows %}
                 <tr>
                     <td>
-                        <input type="checkbox" name="selected-submissions" class="select-submission" value="{{ row.model_id }}" />
+                        <input type="checkbox" name="selected-submissions" class="select-submission" value="{{ row.model_id }}" data-action="w-bulk#toggle" data-w-bulk-target="item" />
                     </td>
                     {% for cell in row.fields %}
                         <td>
diff --git a/wagtail/contrib/forms/templates/wagtailforms/submissions_index.html b/wagtail/contrib/forms/templates/wagtailforms/submissions_index.html
index 663e56f67c..335e039d0c 100644
--- a/wagtail/contrib/forms/templates/wagtailforms/submissions_index.html
+++ b/wagtail/contrib/forms/templates/wagtailforms/submissions_index.html
@@ -21,62 +21,6 @@
                 timepicker: false,
                 format: 'Y-m-d',
             });
-
-            var selectAllCheckbox = document.getElementById('select-all');
-            var deleteButton = document.getElementById('delete-submissions');
-
-            function updateActions() {
-                var submissionCheckboxes = $('input[type=checkbox].select-submission');
-                var someSubmissionsSelected = submissionCheckboxes.is(':checked');
-                var everySubmissionSelected = !submissionCheckboxes.is(':not(:checked)');
-
-                // Select all box state
-                if (everySubmissionSelected) {
-                    // Every submission has been selected
-                    selectAllCheckbox.checked = true;
-                    selectAllCheckbox.indeterminate = false;
-                } else if (someSubmissionsSelected) {
-                    // At least one, but not all submissions have been selected
-                    selectAllCheckbox.checked = false;
-                    selectAllCheckbox.indeterminate = true;
-                } else {
-                    // No submissions have been selected
-                    selectAllCheckbox.checked = false;
-                    selectAllCheckbox.indeterminate = false;
-                }
-
-                // Delete button state
-                if (someSubmissionsSelected) {
-                    deleteButton.classList.remove('disabled')
-                    deleteButton.style.visibility = "visible";
-                } else {
-                    deleteButton.classList.add('disabled')
-                    deleteButton.style.visibility = "hidden";
-                }
-            }
-
-
-            // Event handlers
-
-            $(selectAllCheckbox).on('change', function() {
-                let checked = this.checked;
-
-                // Update checkbox states
-                $('input[type=checkbox].select-submission').each(function() {
-                    this.checked = checked;
-                });
-
-                updateActions();
-            });
-
-            $('input[type=checkbox].select-submission').on('change', function() {
-                updateActions();
-            });
-
-            // initial call to updateActions to bring delete button state in sync with checkboxes
-            // in the case that some checkboxes are pre-checked (which will be the case in some
-            // browsers when using the back button)
-            updateActions();
         });
     </script>
 {% endblock %}