diff --git a/core/modules/media_library/src/Plugin/Field/FieldWidget/MediaLibraryWidget.php b/core/modules/media_library/src/Plugin/Field/FieldWidget/MediaLibraryWidget.php
index 3f341f54b06c4f22c9aa13921d9c7c0d254b5121..7e76940a0b35981ec5ddb827672d0d897f0aa7cf 100644
--- a/core/modules/media_library/src/Plugin/Field/FieldWidget/MediaLibraryWidget.php
+++ b/core/modules/media_library/src/Plugin/Field/FieldWidget/MediaLibraryWidget.php
@@ -286,6 +286,22 @@ public function form(FieldItemListInterface $items, array &$form, FormStateInter
     return parent::form($items, $form, $form_state, $get_delta);
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function extractFormValues(FieldItemListInterface $items, array $form, FormStateInterface $form_state) {
+    parent::extractFormValues($items, $form, $form_state);
+
+    // Update reference to 'items' stored during add or remove to take into
+    // account changes to values like 'weight' etc.
+    // @see Drupal\media_library\Plugin\Field\FieldWidget\MediaLibraryWidget::addItems
+    // @see Drupal\media_library\Plugin\Field\FieldWidget\MediaLibraryWidget::removeItem
+    $field_name = $this->fieldDefinition->getName();
+    $field_state = static::getWidgetState($form['#parents'], $field_name, $form_state);
+    $field_state['items'] = $items->getValue();
+    static::setWidgetState($form['#parents'], $field_name, $form_state, $field_state);
+  }
+
   /**
    * {@inheritdoc}
    */
@@ -744,7 +760,15 @@ public static function updateWidget(array $form, FormStateInterface $form_state)
    *   The form state.
    */
   public static function removeItem(array $form, FormStateInterface $form_state) {
+    // During the form rebuild, formElement() will create field item widget
+    // elements using re-indexed deltas, so clear out FormState::$input to
+    // avoid a mismatch between old and new deltas. The rebuilt elements will
+    // have #default_value set appropriately for the current state of the field,
+    // so nothing is lost in doing this.
+    // @see Drupal\media_library\Plugin\Field\FieldWidget\MediaLibraryWidget::extractFormValues
     $triggering_element = $form_state->getTriggeringElement();
+    $parents = array_slice($triggering_element['#parents'], 0, -2);
+    NestedArray::setValue($form_state->getUserInput(), $parents, NULL);
 
     // Get the parents required to find the top-level widget element.
     if (count($triggering_element['#array_parents']) < 4) {
@@ -844,7 +868,17 @@ public static function validateItems(array $form, FormStateInterface $form_state
    *   The form state.
    */
   public static function addItems(array $form, FormStateInterface $form_state) {
+    // During the form rebuild, formElement() will create field item widget
+    // elements using re-indexed deltas, so clear out FormState::$input to
+    // avoid a mismatch between old and new deltas. The rebuilt elements will
+    // have #default_value set appropriately for the current state of the field,
+    // so nothing is lost in doing this.
+    // @see Drupal\media_library\Plugin\Field\FieldWidget\MediaLibraryWidget::extractFormValues
     $button = $form_state->getTriggeringElement();
+    $parents = array_slice($button['#parents'], 0, -1);
+    $parents[] = 'selection';
+    NestedArray::setValue($form_state->getUserInput(), $parents, NULL);
+
     $element = NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -1));
 
     $field_state = static::getFieldState($element, $form_state);
diff --git a/core/modules/media_library/tests/src/FunctionalJavascript/EntityReferenceWidgetTest.php b/core/modules/media_library/tests/src/FunctionalJavascript/EntityReferenceWidgetTest.php
index 5ec06908ffecefa93899b6cd1586149327345546..31c00338290cad803ae50d0a5a52a69aacc7fe1e 100644
--- a/core/modules/media_library/tests/src/FunctionalJavascript/EntityReferenceWidgetTest.php
+++ b/core/modules/media_library/tests/src/FunctionalJavascript/EntityReferenceWidgetTest.php
@@ -3,6 +3,7 @@
 namespace Drupal\Tests\media_library\FunctionalJavascript;
 
 use Drupal\field\Entity\FieldConfig;
+use Drupal\FunctionalJavascriptTests\SortableTestTrait;
 
 /**
  * Tests the Media library entity reference widget.
@@ -11,6 +12,8 @@
  */
 class EntityReferenceWidgetTest extends MediaLibraryTestBase {
 
+  use SortableTestTrait;
+
   /**
    * {@inheritdoc}
    */
@@ -256,6 +259,7 @@ public function testWidget() {
     $this->openMediaLibraryForField('field_twin_media');
     $page->checkField('Select Dog');
     $this->pressInsertSelected('Added one media item.');
+    $this->waitForElementsCount('css', '.field--name-field-twin-media [data-media-library-item-delta]', 2);
     // Assert that we can toggle the visibility of the weight inputs when the
     // field contains more than one item.
     $wrapper = $assert_session->elementExists('css', '.field--name-field-twin-media');
@@ -485,4 +489,86 @@ public function testRequiredMediaField() {
     $this->assertSession()->pageTextContains('Basic page My page has been created.');
   }
 
+  /**
+   * Tests that changed order is maintained after removing a selection.
+   */
+  public function testRemoveAfterReordering(): void {
+    $assert_session = $this->assertSession();
+    $page = $this->getSession()->getPage();
+
+    $this->drupalGet('node/add/basic_page');
+    $page->fillField('Title', 'My page');
+
+    $this->openMediaLibraryForField('field_unlimited_media');
+    $page->checkField('Select Dog');
+    $page->checkField('Select Cat');
+    $page->checkField('Select Bear');
+    // Order: Dog - Cat - Bear.
+    $this->pressInsertSelected('Added 3 media items.');
+
+    // Move first item (Dog) to the end.
+    // Order: Cat - Bear - Dog.
+    $this->sortableAfter('[data-media-library-item-delta="0"]', '[data-media-library-item-delta="2"]', '.js-media-library-selection');
+
+    $wrapper = $assert_session->elementExists('css', '.field--name-field-unlimited-media');
+    // Remove second item (Bear).
+    // Order: Cat - Dog.
+    $wrapper->find('css', "[aria-label='Remove Bear']")->press();
+    $this->waitForText('Bear has been removed.');
+    $page->pressButton('Save');
+
+    $assert_session->elementTextContains('css', '.field--name-field-unlimited-media > .field__items > .field__item:last-child', 'Dog');
+  }
+
+  /**
+   * Tests that order is correct after re-order and adding another item.
+   */
+  public function testAddAfterReordering(): void {
+    $assert_session = $this->assertSession();
+    $page = $this->getSession()->getPage();
+
+    $this->drupalGet('node/add/basic_page');
+    $page->fillField('Title', 'My page');
+
+    $this->openMediaLibraryForField('field_unlimited_media');
+    $page->checkField('Select Dog');
+    $page->checkField('Select Cat');
+    // Order: Dog - Cat.
+    $this->pressInsertSelected('Added 2 media items.');
+
+    // Change positions.
+    // Order: Cat - Dog.
+    $this->sortableAfter('[data-media-library-item-delta="0"]', '[data-media-library-item-delta="1"]', '.js-media-library-selection');
+
+    $this->openMediaLibraryForField('field_unlimited_media');
+    $this->selectMediaItem(2);
+    // Order: Cat - Dog - Bear.
+    $this->pressInsertSelected('Added one media item.');
+
+    $page->pressButton('Save');
+
+    $assert_session->elementTextContains('css', '.field--name-field-unlimited-media > .field__items > .field__item:first-child', 'Cat');
+    $assert_session->elementTextContains('css', '.field--name-field-unlimited-media > .field__items > .field__item:last-child', 'Bear');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function sortableUpdate($item, $from, $to = NULL) {
+    // See core/modules/media_library/js/media_library.widget.es6.js.
+    $script = <<<JS
+(function ($) {
+    var selection = document.querySelectorAll('.js-media-library-selection');
+    selection.forEach(function (widget) {
+        $(widget).children().each(function (index, child) {
+            $(child).find('.js-media-library-item-weight').val(index);
+        });
+    });
+})(jQuery)
+
+JS;
+
+    $this->getSession()->executeScript($script);
+  }
+
 }