diff --git a/core/modules/comment/config/optional/views.view.comment.yml b/core/modules/comment/config/optional/views.view.comment.yml
index a54fb25883ddf326c54b3b4dadce1f88ec477ef1..1fb1ca0a48229fa94dfc198136b8a44347674e79 100644
--- a/core/modules/comment/config/optional/views.view.comment.yml
+++ b/core/modules/comment/config/optional/views.view.comment.yml
@@ -75,6 +75,9 @@ display:
           selected_actions:
             - comment_delete_action
             - comment_unpublish_action
+          actions_order:
+            - comment_unpublish_action
+            - comment_delete_action
         subject:
           id: subject
           table: comment_field_data
@@ -957,6 +960,9 @@ display:
           selected_actions:
             - comment_delete_action
             - comment_publish_action
+          actions_order:
+            - comment_publish_action
+            - comment_delete_action
         subject:
           id: subject
           table: comment_field_data
diff --git a/core/modules/comment/tests/src/Kernel/Views/CommentAdminViewTest.php b/core/modules/comment/tests/src/Kernel/Views/CommentAdminViewTest.php
index 177a25335bb20a8d2461498fd8c969c192f3592f..83c34172624706ec91f074fbc6d620eeece516be 100644
--- a/core/modules/comment/tests/src/Kernel/Views/CommentAdminViewTest.php
+++ b/core/modules/comment/tests/src/Kernel/Views/CommentAdminViewTest.php
@@ -37,11 +37,13 @@ class CommentAdminViewTest extends ViewsKernelTestBase {
    * {@inheritdoc}
    */
   protected static $modules = [
-    'user',
     'comment',
     'entity_test',
+    'field',
     'language',
     'locale',
+    'text',
+    'user',
   ];
 
   /**
@@ -54,7 +56,7 @@ protected function setUp($import_test_views = TRUE): void {
     $this->installEntitySchema('comment');
     $this->installEntitySchema('entity_test');
     // Create the anonymous role.
-    $this->installConfig(['user']);
+    $this->installConfig(['comment', 'user']);
 
     // Create user 1 so that the user created later in the test has a different
     // user ID.
diff --git a/core/modules/media/config/optional/views.view.media.yml b/core/modules/media/config/optional/views.view.media.yml
index c7a32129b33e023e5f95384e79d17e1436d3d29d..03a5d9f6fbdb5bada8c887938fd936507f10d872 100644
--- a/core/modules/media/config/optional/views.view.media.yml
+++ b/core/modules/media/config/optional/views.view.media.yml
@@ -76,6 +76,11 @@ display:
           action_title: Action
           include_exclude: exclude
           selected_actions: {  }
+          actions_order:
+            - media_publish_action
+            - media_unpublish_action
+            - media_save_action
+            - media_delete_action
         thumbnail__target_id:
           id: thumbnail__target_id
           table: media_field_data
diff --git a/core/modules/media/tests/modules/media_test_views/config/install/views.view.test_media_bulk_form.yml b/core/modules/media/tests/modules/media_test_views/config/install/views.view.test_media_bulk_form.yml
index 8f7f0c8493df848b68dbb53321024455dbd59dc0..5e3339402fe1d52829fdafe335a8058df44a7501 100644
--- a/core/modules/media/tests/modules/media_test_views/config/install/views.view.test_media_bulk_form.yml
+++ b/core/modules/media/tests/modules/media_test_views/config/install/views.view.test_media_bulk_form.yml
@@ -41,6 +41,11 @@ display:
           action_title: 'With selection'
           include_exclude: exclude
           selected_actions: {  }
+          actions_order:
+            - media_publish_action
+            - media_unpublish_action
+            - media_save_action
+            - media_delete_action
         name:
           id: name
           table: media_field_data
diff --git a/core/modules/media/tests/src/Functional/MediaBulkFormTest.php b/core/modules/media/tests/src/Functional/MediaBulkFormTest.php
index 85c1d59eb15d8cb925ea5152c50e94adba59f523..a9bc842cc991d3b3ee5a5ac9b44c7110f308a4b9 100644
--- a/core/modules/media/tests/src/Functional/MediaBulkFormTest.php
+++ b/core/modules/media/tests/src/Functional/MediaBulkFormTest.php
@@ -4,6 +4,7 @@
 
 namespace Drupal\Tests\media\Functional;
 
+use Behat\Mink\Element\NodeElement;
 use Drupal\media\Entity\Media;
 use Drupal\views\Views;
 
@@ -74,16 +75,18 @@ public function testBulkForm(): void {
 
     // Check the operations are accessible to the logged in user.
     $this->drupalGet('test-media-bulk-form');
-    // Current available actions: Delete, Save, Publish, Unpublish.
-    $available_actions = [
-      'media_delete_action',
+
+    // Tests that actions are sorted according to the view configured order.
+    $actual_actions = $this->xpath('//select[@id="edit-action"]//option');
+    $expected_actions = [
       'media_publish_action',
-      'media_save_action',
       'media_unpublish_action',
+      'media_save_action',
+      'media_delete_action',
     ];
-    foreach ($available_actions as $action_name) {
-      $assert_session->optionExists('action', $action_name);
-    }
+    $this->assertSame($expected_actions, array_values(array_filter(array_map(function (NodeElement $action): string {
+      return $action->getValue();
+    }, $actual_actions))));
 
     // Test unpublishing in bulk.
     $page->checkField('media_bulk_form[0]');
diff --git a/core/modules/media_library/config/install/views.view.media_library.yml b/core/modules/media_library/config/install/views.view.media_library.yml
index 5450c17c9379fad2f10f36f5032d86e2f30419bf..1ead90981a008842be561f70c3a6f126f9650f43 100644
--- a/core/modules/media_library/config/install/views.view.media_library.yml
+++ b/core/modules/media_library/config/install/views.view.media_library.yml
@@ -81,6 +81,11 @@ display:
           action_title: Action
           include_exclude: exclude
           selected_actions: {  }
+          actions_order:
+            - media_publish_action
+            - media_unpublish_action
+            - media_save_action
+            - media_delete_action
         rendered_entity:
           id: rendered_entity
           table: media
@@ -537,6 +542,11 @@ display:
           action_title: Action
           include_exclude: exclude
           selected_actions: {  }
+          actions_order:
+            - media_publish_action
+            - media_save_action
+            - media_unpublish_action
+            - media_delete_action
         name:
           id: name
           table: media_field_data
diff --git a/core/modules/media_library/tests/src/FunctionalJavascript/MediaOverviewTest.php b/core/modules/media_library/tests/src/FunctionalJavascript/MediaOverviewTest.php
index a88502c65961e610c943a8bc52be59e2ab630987..beb86d9a6860a355c907623ea7c2c814052485f0 100644
--- a/core/modules/media_library/tests/src/FunctionalJavascript/MediaOverviewTest.php
+++ b/core/modules/media_library/tests/src/FunctionalJavascript/MediaOverviewTest.php
@@ -4,6 +4,8 @@
 
 namespace Drupal\Tests\media_library\FunctionalJavascript;
 
+use Behat\Mink\Element\NodeElement;
+
 /**
  * Tests the grid-style media overview page.
  *
@@ -66,6 +68,18 @@ public function testAdministrationPage(): void {
     // Visit the administration page.
     $this->drupalGet('admin/content/media');
 
+    // Tests that actions are sorted according to the view configured order.
+    $actual_actions = $this->xpath('//select[@id="edit-action"]//option');
+    $expected_actions = [
+      'media_publish_action',
+      'media_unpublish_action',
+      'media_save_action',
+      'media_delete_action',
+    ];
+    $this->assertSame($expected_actions, array_values(array_filter(array_map(function (NodeElement $action): string {
+      return $action->getValue();
+    }, $actual_actions))));
+
     // There should be links to both the grid and table displays.
     $assert_session->linkExists('Grid');
     $assert_session->linkExists('Table');
@@ -116,6 +130,7 @@ public function testAdministrationPage(): void {
 
     // This tests that anchor tags clicked inside the preview are suppressed.
     $this->getSession()->executeScript('jQuery(".js-click-to-select-trigger a")[4].click()');
+    $this->getSession()->getPage()->selectFieldOption('Action', 'Delete media');
     $this->submitForm([], 'Apply to selected items');
     $assert_session->pageTextContains('Dog');
     $assert_session->pageTextNotContains('Cat');
diff --git a/core/modules/node/config/optional/views.view.content.yml b/core/modules/node/config/optional/views.view.content.yml
index d0bbb09cf5a0963ab532b4b28b78e3f9e92095e8..8e12ec15e8b8995f6379c7598e344b1f88c608af 100644
--- a/core/modules/node/config/optional/views.view.content.yml
+++ b/core/modules/node/config/optional/views.view.content.yml
@@ -36,6 +36,18 @@ display:
           hide_empty: false
           empty_zero: false
           hide_alter_empty: true
+          action_title: Action
+          include_exclude: exclude
+          selected_actions: {  }
+          actions_order:
+            - node_publish_action
+            - node_unpublish_action
+            - node_promote_action
+            - node_unpromote_action
+            - node_make_sticky_action
+            - node_make_unsticky_action
+            - node_save_action
+            - node_delete_action
         title:
           id: title
           table: node_field_data
diff --git a/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_bulk_form.yml b/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_bulk_form.yml
index d7a3fa080fed21b4077504d9f3eb15e5556fe36f..0f67e50640ea5b8a136389bf549f851e3a80b6e5 100644
--- a/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_bulk_form.yml
+++ b/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_bulk_form.yml
@@ -28,6 +28,18 @@ display:
           field: node_bulk_form
           plugin_id: node_bulk_form
           entity_type: node
+          action_title: Action
+          include_exclude: exclude
+          selected_actions: {  }
+          actions_order:
+            - node_publish_action
+            - node_unpublish_action
+            - node_promote_action
+            - node_unpromote_action
+            - node_make_sticky_action
+            - node_make_unsticky_action
+            - node_save_action
+            - node_delete_action
         title:
           id: title
           table: node_field_data
diff --git a/core/modules/node/tests/src/Functional/Views/BulkFormTest.php b/core/modules/node/tests/src/Functional/Views/BulkFormTest.php
index 8514b3cee00d6298ea88c1c1fb7b5c8fa65ce4a8..d04dad1070bd2bd4cc8839ddbfd32811e9ca9d85 100644
--- a/core/modules/node/tests/src/Functional/Views/BulkFormTest.php
+++ b/core/modules/node/tests/src/Functional/Views/BulkFormTest.php
@@ -4,6 +4,7 @@
 
 namespace Drupal\Tests\node\Functional\Views;
 
+use Behat\Mink\Element\NodeElement;
 use Drupal\language\Entity\ConfigurableLanguage;
 use Drupal\views\Views;
 
@@ -103,6 +104,30 @@ protected function setUp($import_test_views = TRUE, $modules = ['node_test_views
    * Tests the node bulk form.
    */
   public function testBulkForm(): void {
+    // Tests that actions are sorted according to the view configured order.
+    $actual_actions = $this->xpath('//select[@id="edit-action"]//option');
+    $expected_actions = [
+      'node_publish_action',
+      'node_unpublish_action',
+      'node_promote_action',
+      'node_unpromote_action',
+      'node_make_sticky_action',
+      'node_make_unsticky_action',
+      'node_save_action',
+      'node_delete_action',
+    ];
+    $this->assertSame(
+      $expected_actions,
+      array_values(
+        array_filter(
+          array_map(
+            fn(NodeElement $action): string => $action->getValue(),
+            $actual_actions,
+          ),
+        ),
+      ),
+    );
+
     // Unpublish a node using the bulk form.
     $node = reset($this->nodes);
     $this->assertTrue($node->isPublished(), 'Node is initially published');
diff --git a/core/modules/user/config/optional/views.view.user_admin_people.yml b/core/modules/user/config/optional/views.view.user_admin_people.yml
index 5f9c0667cace5787eb2ebb7096dda25e001866f7..0eeb33a43d5865212799295fdc145218f0af39d2 100644
--- a/core/modules/user/config/optional/views.view.user_admin_people.yml
+++ b/core/modules/user/config/optional/views.view.user_admin_people.yml
@@ -69,6 +69,10 @@ display:
           hide_empty: false
           empty_zero: false
           hide_alter_empty: true
+          action_title: Action
+          include_exclude: exclude
+          selected_actions: {  }
+          actions_order: []
         name:
           id: name
           table: users_field_data
diff --git a/core/modules/user/tests/src/Functional/Views/BulkFormTest.php b/core/modules/user/tests/src/Functional/Views/BulkFormTest.php
index 4047df4eadbbe1095f19f899b247a9c01f0594f6..9c1704f74cc82b46ffe9aa761a79e5b1c2864e4f 100644
--- a/core/modules/user/tests/src/Functional/Views/BulkFormTest.php
+++ b/core/modules/user/tests/src/Functional/Views/BulkFormTest.php
@@ -121,7 +121,7 @@ public function testBulkForm(): void {
     $action_id = 'user_add_role_action.' . $role;
     $edit = [
       'options[include_exclude]' => 'exclude',
-      "options[selected_actions][$action_id]" => $action_id,
+      "options[selected_actions][$action_id][selected]" => TRUE,
     ];
     $this->drupalGet('admin/structure/views/nojs/handler/test_user_bulk_form/default/field/user_bulk_form');
     $this->submitForm($edit, 'Apply');
diff --git a/core/modules/views/config/schema/views.data_types.schema.yml b/core/modules/views/config/schema/views.data_types.schema.yml
index 0e011d73dc15f54451fbd29ea4f28ca02369877e..ef213cf4bf628ad7823435bc5cd42f2df659d523 100644
--- a/core/modules/views/config/schema/views.data_types.schema.yml
+++ b/core/modules/views/config/schema/views.data_types.schema.yml
@@ -921,3 +921,9 @@ views_field_bulk_form:
       sequence:
         type: string
         label: 'Action'
+    actions_order:
+      type: sequence
+      label: 'Actions order'
+      sequence:
+        type: string
+        label: 'Action'
diff --git a/core/modules/views/css/views.bulk_form.css b/core/modules/views/css/views.bulk_form.css
new file mode 100644
index 0000000000000000000000000000000000000000..488c792d63f6793219e75efb43fef08b0fd78d4b
--- /dev/null
+++ b/core/modules/views/css/views.bulk_form.css
@@ -0,0 +1,12 @@
+tr.draggable td {
+  width: 100%;
+}
+tr.draggable td:first-child {
+  display: flex;
+  align-items: center;
+  width: 2.5rem;
+}
+/* Ensure a space between checkbox and the 'Changed' asterisk */
+tr.draggable td:first-child input[type="checkbox"] {
+  margin-right: 3px;
+}
diff --git a/core/modules/views/src/Plugin/views/field/BulkForm.php b/core/modules/views/src/Plugin/views/field/BulkForm.php
index 2fcf8588bb78aac7e93f42242a353a1a902d082d..ce20dee14672d6a470f96a06ddbf034a9c99a15d 100644
--- a/core/modules/views/src/Plugin/views/field/BulkForm.php
+++ b/core/modules/views/src/Plugin/views/field/BulkForm.php
@@ -217,6 +217,9 @@ protected function defineOptions() {
     $options['selected_actions'] = [
       'default' => [],
     ];
+    $options['actions_order'] = [
+      'default' => [],
+    ];
     return $options;
   }
 
@@ -240,13 +243,52 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) {
       ],
       '#default_value' => $this->options['include_exclude'],
     ];
+
     $form['selected_actions'] = [
-      '#type' => 'checkboxes',
+      '#type' => 'table',
       '#title' => $this->t('Selected actions'),
-      '#options' => $this->getBulkOptions(FALSE),
-      '#default_value' => $this->options['selected_actions'],
+      '#tabledrag' => [
+        [
+          'action' => 'order',
+          'relationship' => 'sibling',
+          'group' => 'action-order-weight',
+        ],
+      ],
+      '#tree' => FALSE,
+      '#input' => FALSE,
+      '#theme_wrappers' => ['form_element'],
+      '#attached' => ['library' => ['views/views.bulk_form']],
     ];
 
+    $weight = 0;
+    foreach ($this->getBulkOptions(FALSE) as $action_id => $action_label) {
+      $form['selected_actions'][$action_id]['#attributes']['class'][] = 'draggable';
+      $form['selected_actions'][$action_id]['#weight'] = $weight++;
+
+      $arguments = ['@title' => $action_label];
+      $form['selected_actions'][$action_id]['selected'] = [
+        '#type' => 'checkbox',
+        '#title' => $this->t('Select @title', $arguments),
+        '#title_display' => 'attribute',
+        '#default_value' => in_array($action_id, $this->options['selected_actions'], TRUE),
+        '#parents' => ['options', 'selected_actions', $action_id, 'selected'],
+      ];
+
+      $form['selected_actions'][$action_id]['action'] = [
+        '#markup' => $action_label,
+      ];
+
+      $form['selected_actions'][$action_id]['weight'] = [
+        '#type' => 'weight',
+        '#title' => $this->t('Weight for @title', $arguments),
+        '#title_display' => 'invisible',
+        '#delta' => 50,
+        '#default_value' => $weight,
+        '#parents' => ['options', 'selected_actions', $action_id, 'weight'],
+        '#attributes' => ['class' => ['action-order-weight']],
+      ];
+
+    }
     parent::buildOptionsForm($form, $form_state);
   }
 
@@ -256,8 +298,16 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) {
   public function validateOptionsForm(&$form, FormStateInterface $form_state) {
     parent::validateOptionsForm($form, $form_state);
 
-    $selected_actions = $form_state->getValue(['options', 'selected_actions']);
-    $form_state->setValue(['options', 'selected_actions'], array_values(array_filter($selected_actions)));
+    $selected_actions = $actions_order = [];
+    foreach ($form_state->getValue(['options', 'selected_actions']) as $action_id => $action_data) {
+      if ($action_data['selected']) {
+        $selected_actions[] = $action_id;
+      }
+      $actions_order[$action_id] = (int) $action_data['weight'];
+    }
+    $form_state->setValue(['options', 'selected_actions'], $selected_actions);
+    asort($actions_order);
+    $form_state->setValue(['options', 'actions_order'], array_keys($actions_order));
   }
 
   /**
@@ -327,6 +377,11 @@ public function viewsForm(&$form, FormStateInterface $form_state) {
 
       // Replace the form submit button label.
       $form['actions']['submit']['#value'] = $this->t('Apply to selected items');
+      $form['actions']['submit']['#states'] = [
+        'disabled' => [
+          'select[name="action"]' => ['value' => ''],
+        ],
+      ];
 
       // Ensure a consistent container for filters/operations in the view
       // header.
@@ -345,6 +400,7 @@ public function viewsForm(&$form, FormStateInterface $form_state) {
         '#title' => $this->options['action_title'],
         '#options' => $this->getBulkOptions(),
         '#empty_option' => $this->t('- Select -'),
+        '#required' => TRUE,
       ];
 
       // Duplicate the form actions into the action container in the header.
@@ -367,8 +423,18 @@ public function viewsForm(&$form, FormStateInterface $form_state) {
    */
   protected function getBulkOptions($filtered = TRUE) {
     $options = [];
+
+    $actions = $this->actions;
+
+    // Only order if there is a user specified order.
+    if ($actions && $this->options['actions_order']) {
+      // Some actions might have been removed. Keep only existing actions.
+      $actions_order = array_intersect($this->options['actions_order'], array_keys($actions));
+      $actions = array_merge(array_flip($actions_order), $actions);
+    }
+
     // Filter the action list.
-    foreach ($this->actions as $id => $action) {
+    foreach ($actions as $id => $action) {
       if ($filtered) {
         $in_selected = in_array($id, $this->options['selected_actions']);
         // If the field is configured to include only the selected actions,
diff --git a/core/modules/views/src/ViewsConfigUpdater.php b/core/modules/views/src/ViewsConfigUpdater.php
index 3b2709cc60c2ac7d43d8279bc8bb30c61d5220e4..b2f4add5155bd190639057bcb172ebd1c9d59d71 100644
--- a/core/modules/views/src/ViewsConfigUpdater.php
+++ b/core/modules/views/src/ViewsConfigUpdater.php
@@ -7,6 +7,7 @@
 use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
 use Drupal\Core\Entity\EntityFieldManagerInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\views\Plugin\views\field\BulkForm;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -131,6 +132,9 @@ public function updateAll(ViewEntityInterface $view) {
       if ($this->processEntityArgumentUpdate($view)) {
         $changed = TRUE;
       }
+      if ($this->processBulkFormActionsOrderUpdate($handler, $handler_type)) {
+        $changed = TRUE;
+      }
       if ($this->processRememberRolesUpdate($handler, $handler_type)) {
         $changed = TRUE;
       }
@@ -349,4 +353,45 @@ public function processTableCssClassUpdate(ViewEntityInterface $view): bool {
     return $changed;
   }
 
+  /**
+   * Updates the bulk form fields by adding the actions order configuration.
+   *
+   * @param \Drupal\views\ViewEntityInterface $view
+   *   The View to update.
+   *
+   * @return bool
+   *   Whether the view was updated.
+   */
+  public function needsBulkFormActionsOrderUpdate(ViewEntityInterface $view): bool {
+    return $this->processDisplayHandlers($view, FALSE, function (array &$handler, string $handler_type): bool {
+      return $this->processBulkFormActionsOrderUpdate($handler, $handler_type);
+    });
+  }
+
+  /**
+   * Processes the bulk form fields by adding the actions order configuration.
+   *
+   * @param array $handler
+   *   A display handler.
+   * @param string $handler_type
+   *   The handler type.
+   *
+   * @return bool
+   *   Whether the handler was updated.
+   */
+  protected function processBulkFormActionsOrderUpdate(array &$handler, string $handler_type): bool {
+    if ($handler_type === 'field' && !isset($handler['actions_order']) && isset($handler['plugin_id'])) {
+      /** @var \Drupal\views\Plugin\ViewsPluginManager $plugin_manager */
+      $plugin_manager = \Drupal::service('plugin.manager.views.field');
+      if ($plugin_manager->hasDefinition($handler['plugin_id'])) {
+        $definition = $plugin_manager->getDefinition($handler['plugin_id']);
+        if (is_subclass_of($definition['class'], BulkForm::class)) {
+          $handler['actions_order'] = [];
+          return TRUE;
+        }
+      }
+    }
+    return FALSE;
+  }
+
 }
diff --git a/core/modules/views/tests/modules/action_bulk_test/config/install/views.view.test_bulk_form.yml b/core/modules/views/tests/modules/action_bulk_test/config/install/views.view.test_bulk_form.yml
index cba11476ae8b12c99bfc03d4c460012128ace83d..7ce2b00b0c3b4859155fc25965a7f0bc28295f3a 100644
--- a/core/modules/views/tests/modules/action_bulk_test/config/install/views.view.test_bulk_form.yml
+++ b/core/modules/views/tests/modules/action_bulk_test/config/install/views.view.test_bulk_form.yml
@@ -89,6 +89,18 @@ display:
           hide_empty: false
           empty_zero: false
           hide_alter_empty: true
+          action_title: Action
+          include_exclude: exclude
+          selected_actions: {  }
+          actions_order:
+            - node_publish_action
+            - node_unpublish_action
+            - node_promote_action
+            - node_unpromote_action
+            - node_make_sticky_action
+            - node_make_unsticky_action
+            - node_save_action
+            - node_delete_action
       pager:
         type: full
         options:
diff --git a/core/modules/views/tests/src/Functional/BulkFormTest.php b/core/modules/views/tests/src/Functional/BulkFormTest.php
index 842b9ff642bc67b8cc0cd22fa04badf4f6512e26..6badbed7b25ce8a7503954c33d00dd499cb56ac5 100644
--- a/core/modules/views/tests/src/Functional/BulkFormTest.php
+++ b/core/modules/views/tests/src/Functional/BulkFormTest.php
@@ -4,6 +4,7 @@
 
 namespace Drupal\Tests\views\Functional;
 
+use Behat\Mink\Element\NodeElement;
 use Drupal\Tests\BrowserTestBase;
 use Drupal\Tests\node\Traits\NodeCreationTrait;
 use Drupal\views\Views;
@@ -71,6 +72,55 @@ public function testBulkForm(): void {
 
     $this->drupalGet('test_bulk_form');
 
+    // Tests that actions are sorted according to the view configured order.
+    $actual_actions = $this->xpath('//select[@id="edit-action"]//option');
+    $expected_actions = [
+      'node_publish_action',
+      'node_unpublish_action',
+      'node_promote_action',
+      'node_unpromote_action',
+      'node_make_sticky_action',
+      'node_make_unsticky_action',
+      'node_save_action',
+      'node_delete_action',
+      'test_action',
+    ];
+    $this->assertSame($expected_actions, array_values(array_filter(array_map(function (NodeElement $action): string {
+      return $action->getValue();
+    }, $actual_actions))));
+
+    // Test changing the order of the actions.
+    $view = Views::getView('test_bulk_form');
+    $display = &$view->storage->getDisplay('default');
+    $display['display_options']['fields']['node_bulk_form']['actions_order'] = [
+      'node_make_unsticky_action',
+      'node_publish_action',
+      'node_make_sticky_action',
+      'node_save_action',
+      'test_action',
+      'node_unpromote_action',
+      'node_delete_action',
+      'node_unpublish_action',
+      'node_promote_action',
+    ];
+    $view->save();
+    $this->getSession()->reload();
+    $actual_actions = $this->xpath('//select[@id="edit-action"]//option');
+    $expected_actions = [
+      'node_make_unsticky_action',
+      'node_publish_action',
+      'node_make_sticky_action',
+      'node_save_action',
+      'test_action',
+      'node_unpromote_action',
+      'node_delete_action',
+      'node_unpublish_action',
+      'node_promote_action',
+    ];
+    $this->assertSame($expected_actions, array_values(array_filter(array_map(function (NodeElement $action): string {
+      return $action->getValue();
+    }, $actual_actions))));
+
     // Test that the views edit header appears first.
     $this->assertSession()->elementExists('xpath', '//form/div[1][@id = "edit-header"]');
 
diff --git a/core/modules/views/tests/src/Functional/Plugin/ViewsBulkTest.php b/core/modules/views/tests/src/Functional/Plugin/ViewsBulkTest.php
index 5c33bcb27a7df07d6d3b97b7bc968ad11f743561..3e7a049f387aab12e9ee2e30207da8dab0f3d11b 100644
--- a/core/modules/views/tests/src/Functional/Plugin/ViewsBulkTest.php
+++ b/core/modules/views/tests/src/Functional/Plugin/ViewsBulkTest.php
@@ -71,7 +71,8 @@ public function testBulkSelection(): void {
 
     // Now click 'Apply to selected items' and assert the first node is selected
     // on the confirm form.
-    $this->submitForm(['node_bulk_form[0]' => TRUE], 'Apply to selected items');
+    $edit = ['node_bulk_form[0]' => TRUE, 'action' => 'node_delete_action'];
+    $this->submitForm($edit, 'Apply to selected items');
     $this->assertSession()->pageTextContains($node_1->getTitle());
     $this->assertSession()->pageTextNotContains($node_2->getTitle());
 
@@ -92,7 +93,8 @@ public function testBulkSelection(): void {
 
     // Now click 'Apply to selected items' and assert the second node is
     // selected on the confirm form.
-    $this->submitForm(['node_bulk_form[1]' => TRUE], 'Apply to selected items');
+    $edit = ['node_bulk_form[1]' => TRUE, 'action' => 'node_delete_action'];
+    $this->submitForm($edit, 'Apply to selected items');
     $this->assertSession()->pageTextContains($node_1->getTitle());
     $this->assertSession()->pageTextNotContains($node_3->getTitle());
   }
diff --git a/core/modules/views/tests/src/Functional/Update/ViewsBulkFormActionsOrderUpdateTest.php b/core/modules/views/tests/src/Functional/Update/ViewsBulkFormActionsOrderUpdateTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..5e127e76d639260859e800cd50258c76893c1c6d
--- /dev/null
+++ b/core/modules/views/tests/src/Functional/Update/ViewsBulkFormActionsOrderUpdateTest.php
@@ -0,0 +1,40 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Tests\views\Functional\Update;
+
+use Drupal\FunctionalTests\Update\UpdatePathTestBase;
+
+/**
+ * Tests views_post_update_bulk_form_action_order().
+ *
+ * @group views
+ * @group legacy
+ */
+class ViewsBulkFormActionsOrderUpdateTest extends UpdatePathTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setDatabaseDumpFiles(): void {
+    $this->databaseDumpFiles = [
+      __DIR__ . '/../../../../../system/tests/fixtures/update/drupal-10.3.0.bare.standard.php.gz',
+    ];
+  }
+
+  /**
+   * @covers  \views_post_update_bulk_form_action_order
+   */
+  public function testBulkFormActionsOrderPostUpdate(): void {
+    $config_factory = \Drupal::configFactory();
+    $path = 'display.default.display_options.fields.user_bulk_form';
+    $config = $config_factory->get('views.view.user_admin_people');
+    $this->assertArrayNotHasKey('actions_order', $config->get($path));
+    $this->runUpdates();
+    $config = $config_factory->get('views.view.user_admin_people');
+    $this->assertArrayHasKey('actions_order', $config->get($path));
+    $this->assertSame([], $config->get("$path.actions_order"));
+  }
+
+}
diff --git a/core/modules/views/views.libraries.yml b/core/modules/views/views.libraries.yml
index 08ff2d1640798b7f9e862b32074392f2f392d7ae..89cc2558c707951a84aed80b417fb40e03f0f5f3 100644
--- a/core/modules/views/views.libraries.yml
+++ b/core/modules/views/views.libraries.yml
@@ -22,3 +22,9 @@ views.responsive-grid:
   css:
     layout:
       css/views-responsive-grid.css: {}
+
+views.bulk_form:
+  version: VERSION
+  css:
+    component:
+      css/views.bulk_form.css: {}
diff --git a/core/modules/views/views.post_update.php b/core/modules/views/views.post_update.php
index c3f352e99a4203c026860c66a84ef43f5d535d4b..018b3098f821803d747dda01a519e76dc95d47af 100644
--- a/core/modules/views/views.post_update.php
+++ b/core/modules/views/views.post_update.php
@@ -87,3 +87,14 @@ function views_post_update_table_css_class(?array &$sandbox = NULL): void {
     return $view_config_updater->needsTableCssClassUpdate($view);
   });
 }
+
+/**
+ * Add the actions order option to all bulk form field configurations.
+ */
+function views_post_update_bulk_form_action_order(?array &$sandbox = NULL): void {
+  /** @var \Drupal\views\ViewsConfigUpdater $view_config_updater */
+  $view_config_updater = \Drupal::classResolver(ViewsConfigUpdater::class);
+  \Drupal::classResolver(ConfigEntityUpdater::class)->update($sandbox, 'view', function (ViewEntityInterface $view) use ($view_config_updater): bool {
+    return $view_config_updater->needsBulkFormActionsOrderUpdate($view);
+  });
+}
diff --git a/core/profiles/demo_umami/config/optional/views.view.media.yml b/core/profiles/demo_umami/config/optional/views.view.media.yml
index 98c7e3ee23763e735f0508e593c5af5504ea10b9..74bc3d55725ceef9dc485d3625c88fa55c49c466 100644
--- a/core/profiles/demo_umami/config/optional/views.view.media.yml
+++ b/core/profiles/demo_umami/config/optional/views.view.media.yml
@@ -76,6 +76,11 @@ display:
           action_title: Action
           include_exclude: exclude
           selected_actions: {  }
+          actions_order:
+            - media_publish_action
+            - media_unpublish_action
+            - media_save_action
+            - media_delete_action
         thumbnail__target_id:
           id: thumbnail__target_id
           table: media_field_data
diff --git a/core/tests/fixtures/config_install/multilingual/views.view.content.yml b/core/tests/fixtures/config_install/multilingual/views.view.content.yml
index 20afac251c06cf65cc3877120c09f63c56ee202a..125070eec7b0489524dd83ac3904ca3e12bbc896 100644
--- a/core/tests/fixtures/config_install/multilingual/views.view.content.yml
+++ b/core/tests/fixtures/config_install/multilingual/views.view.content.yml
@@ -39,6 +39,18 @@ display:
           hide_empty: false
           empty_zero: false
           hide_alter_empty: true
+          action_title: Action
+          include_exclude: exclude
+          selected_actions: {  }
+          actions_order:
+            - node_publish_action
+            - node_unpublish_action
+            - node_promote_action
+            - node_unpromote_action
+            - node_make_sticky_action
+            - node_make_unsticky_action
+            - node_save_action
+            - node_delete_action
         title:
           id: title
           table: node_field_data
diff --git a/core/tests/fixtures/config_install/multilingual/views.view.user_admin_people.yml b/core/tests/fixtures/config_install/multilingual/views.view.user_admin_people.yml
index 4c48d1332245021d3d6b536247e167bbc46b9e26..c2910f7d08b3ad560d5f1d6ba840c689edec6503 100644
--- a/core/tests/fixtures/config_install/multilingual/views.view.user_admin_people.yml
+++ b/core/tests/fixtures/config_install/multilingual/views.view.user_admin_people.yml
@@ -72,6 +72,10 @@ display:
           hide_empty: false
           empty_zero: false
           hide_alter_empty: true
+          action_title: Action
+          include_exclude: exclude
+          selected_actions: {  }
+          actions_order: []
         name:
           id: name
           table: users_field_data
diff --git a/core/themes/claro/claro.info.yml b/core/themes/claro/claro.info.yml
index 87156826550e0c66f732b70c1e454c2c2119c108..4fde9d2bb3ddaae699af639aa4fd223f49e88bc3 100644
--- a/core/themes/claro/claro.info.yml
+++ b/core/themes/claro/claro.info.yml
@@ -97,6 +97,11 @@ libraries-override:
       state:
         css/toolbar.menu.css: css/state/toolbar.menu.css
 
+  views/views.bulk_form:
+    css:
+      component:
+        css/views.bulk_form.css: css/components/views.bulk_form.css
+
   views_ui/admin.styling:
     css:
       component:
diff --git a/core/themes/claro/css/components/views.bulk_form.css b/core/themes/claro/css/components/views.bulk_form.css
new file mode 100644
index 0000000000000000000000000000000000000000..2cae782ff1a3259519d488ef08d68fa45a0cda6f
--- /dev/null
+++ b/core/themes/claro/css/components/views.bulk_form.css
@@ -0,0 +1,12 @@
+tr.draggable td {
+  width: 100%;
+}
+tr.draggable td:first-child {
+  display: flex;
+  align-items: center;
+  width: 5rem;
+}
+/* Ensure a space between checkbox and the 'Changed' asterisk */
+tr.draggable td:first-child input[type="checkbox"] {
+  margin-right: 3px;
+}
diff --git a/core/themes/stable9/css/views/views.bulk_form.css b/core/themes/stable9/css/views/views.bulk_form.css
new file mode 100644
index 0000000000000000000000000000000000000000..488c792d63f6793219e75efb43fef08b0fd78d4b
--- /dev/null
+++ b/core/themes/stable9/css/views/views.bulk_form.css
@@ -0,0 +1,12 @@
+tr.draggable td {
+  width: 100%;
+}
+tr.draggable td:first-child {
+  display: flex;
+  align-items: center;
+  width: 2.5rem;
+}
+/* Ensure a space between checkbox and the 'Changed' asterisk */
+tr.draggable td:first-child input[type="checkbox"] {
+  margin-right: 3px;
+}
diff --git a/core/themes/stable9/stable9.info.yml b/core/themes/stable9/stable9.info.yml
index 71dc76f30b618ec3b37169f57ab83d449b75ce47..8ac2d7b1abbfdd6154ec707dc01cbe30182ea2ee 100644
--- a/core/themes/stable9/stable9.info.yml
+++ b/core/themes/stable9/stable9.info.yml
@@ -302,6 +302,11 @@ libraries-override:
       component:
         css/views.module.css: css/views/views.module.css
 
+  views/views.bulk_form:
+    css:
+      component:
+        css/views.bulk_form.css: css/views/views.bulk_form.css
+
   views_ui/admin.styling:
     css:
       component: