diff --git a/core/lib/Drupal/Core/Field/WidgetBase.php b/core/lib/Drupal/Core/Field/WidgetBase.php
index c3b67ba0b7..4815b59e73 100644
--- a/core/lib/Drupal/Core/Field/WidgetBase.php
+++ b/core/lib/Drupal/Core/Field/WidgetBase.php
@@ -129,7 +129,7 @@ public function form(FieldItemListInterface $items, array &$form, FormStateInter
     // Populate the 'array_parents' information in $form_state->get('field')
     // after the form is built, so that we catch changes in the form structure
     // performed in alter() hooks.
-    $elements['#after_build'][] = [get_class($this), 'afterBuild'];
+    $elements['#after_build'][] = [static::class, 'afterBuild'];
     $elements['#field_name'] = $field_name;
     $elements['#field_parents'] = $parents;
     // Enforce the structure of submitted values.
@@ -165,12 +165,12 @@ protected function formMultipleElements(FieldItemListInterface $items, array &$f
     $field_name = $this->fieldDefinition->getName();
     $cardinality = $this->fieldDefinition->getFieldStorageDefinition()->getCardinality();
     $parents = $form['#parents'];
+    $field_state = static::getWidgetState($parents, $field_name, $form_state);
 
     // Determine the number of widgets to display.
     switch ($cardinality) {
       case FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED:
-        $field_state = static::getWidgetState($parents, $field_name, $form_state);
-        $max = $field_state['items_count'];
+        $max = $field_state['items_count'] - 1;
         $is_multiple = TRUE;
         break;
 
@@ -182,6 +182,8 @@ protected function formMultipleElements(FieldItemListInterface $items, array &$f
 
     $title = $this->fieldDefinition->getLabel();
     $description = FieldFilteredMarkup::create(\Drupal::token()->replace($this->fieldDefinition->getDescription()));
+    $id_prefix = implode('-', array_merge($parents, [$field_name]));
+    $wrapper_id = Html::getUniqueId($id_prefix . '-add-more-wrapper');
 
     $elements = [];
 
@@ -224,45 +226,66 @@ protected function formMultipleElements(FieldItemListInterface $items, array &$f
             '#default_value' => $items[$delta]->_weight ?: $delta,
             '#weight' => 100,
           ];
+
+          $remove_button = [
+            '#delta' => $delta,
+            '#name' => str_replace('-', '_', $id_prefix) . "_{$delta}_add_more_remove_button",
+            '#type' => 'submit',
+            '#value' => $this->t('Remove'),
+            '#validate' => [],
+            '#submit' => [[static::class, 'submitRemove']],
+            '#limit_validation_errors' => [],
+            '#attributes' => [
+              'class' => ['remove-field-delta--' . $delta],
+            ],
+            '#ajax' => [
+              'callback' => [static::class, 'removeAjaxContentRefresh'],
+              'wrapper' => $wrapper_id,
+              'effect' => 'fade',
+            ],
+          ];
+
+          $element['actions'] = [
+            'remove_button' => $remove_button,
+            '#weight' => 101,
+          ];
         }
 
         $elements[$delta] = $element;
       }
     }
 
-    if ($elements) {
-      $elements += [
-        '#theme' => 'field_multiple_value_form',
-        '#field_name' => $field_name,
-        '#cardinality' => $cardinality,
-        '#cardinality_multiple' => $this->fieldDefinition->getFieldStorageDefinition()->isMultiple(),
-        '#required' => $this->fieldDefinition->isRequired(),
-        '#title' => $title,
-        '#description' => $description,
-        '#max_delta' => $max,
+    $elements += [
+      '#theme' => 'field_multiple_value_form',
+      '#field_name' => $field_name,
+      '#cardinality' => $cardinality,
+      '#cardinality_multiple' => $this->fieldDefinition->getFieldStorageDefinition()->isMultiple(),
+      '#required' => $this->fieldDefinition->isRequired(),
+      '#title' => $title,
+      '#description' => $description,
+      '#max_delta' => $max,
+    ];
+
+    // Add 'add more' button, if not working with a programmed form.
+    if ($cardinality == FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED && !$form_state->isProgrammed()) {
+      $elements['#wrapper_id'] = $wrapper_id;
+      $elements['#prefix'] = '<div id="' . $wrapper_id . '">';
+      $elements['#suffix'] = '</div>';
+
+      $elements['add_more'] = [
+        '#type' => 'submit',
+        '#name' => str_replace('-', '_', $id_prefix) . '_add_more',
+        '#value' => $delta > 0 ? $this->t('Add another item') : $this->t('Add item'),
+        '#attributes' => ['class' => ['field-add-more-submit']],
+        '#limit_validation_errors' => [],
+        '#submit' => [[static::class, 'addMoreSubmit']],
+        '#ajax' => [
+          'callback' => [static::class, 'addMoreAjax'],
+          'wrapper' => $wrapper_id,
+          'effect' => 'fade',
+        ],
       ];
 
-      // Add 'add more' button, if not working with a programmed form.
-      if ($cardinality == FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED && !$form_state->isProgrammed()) {
-        $id_prefix = implode('-', array_merge($parents, [$field_name]));
-        $wrapper_id = Html::getUniqueId($id_prefix . '-add-more-wrapper');
-        $elements['#prefix'] = '<div id="' . $wrapper_id . '">';
-        $elements['#suffix'] = '</div>';
-
-        $elements['add_more'] = [
-          '#type' => 'submit',
-          '#name' => strtr($id_prefix, '-', '_') . '_add_more',
-          '#value' => t('Add another item'),
-          '#attributes' => ['class' => ['field-add-more-submit']],
-          '#limit_validation_errors' => [array_merge($parents, [$field_name])],
-          '#submit' => [[get_class($this), 'addMoreSubmit']],
-          '#ajax' => [
-            'callback' => [get_class($this), 'addMoreAjax'],
-            'wrapper' => $wrapper_id,
-            'effect' => 'fade',
-          ],
-        ];
-      }
     }
 
     return $elements;
@@ -329,6 +352,72 @@ public static function addMoreAjax(array $form, FormStateInterface $form_state)
     return $element;
   }
 
+  /**
+   * Ajax submit callback for the "Remove" button.
+   *
+   * This re-numbers form elements and removes an item.
+   */
+  public static function submitRemove(&$form, FormStateInterface $form_state) {
+    $button = $form_state->getTriggeringElement();
+    $delta = (int) $button['#delta'];
+    $array_parents = array_slice($button['#array_parents'], 0, -4);
+    $parent_element = NestedArray::getValue($form, array_merge($array_parents, ['widget']));
+    $field_name = $parent_element['#field_name'];
+    $parents = $parent_element['#field_parents'];
+    $field_state = static::getWidgetState($parents, $field_name, $form_state);
+    $user_input = $form_state->getUserInput();
+    $field_input = NestedArray::getValue($user_input, $parent_element['#parents'], $exists);
+    if ($exists) {
+      $field_values = [];
+      foreach ($field_input as $key => $input) {
+        if (is_numeric($key) && $key >= $delta) {
+          if ((int) $key === $delta) {
+            --$key;
+            continue;
+          }
+        }
+        $field_values[$key] = $input;
+      }
+      NestedArray::setValue($user_input, $parent_element['#parents'], $field_values);
+      $form_state->setUserInput($user_input);
+    }
+
+    unset($parent_element[$delta]);
+    NestedArray::setValue($form, $array_parents, $parent_element);
+
+    if ($field_state['items_count'] > 0) {
+      $field_state['items_count']--;
+    }
+
+    $user_input = $form_state->getUserInput();
+    $input = NestedArray::getValue($user_input, $parent_element['#parents'], $exists);
+    $weight = -1 * $field_state['items_count'];
+    foreach ($input as $key => $item) {
+      if ($item) {
+        $input[$key]['_weight'] = $weight++;
+      }
+    }
+    // Reset indices.
+    $input = array_values($input);
+
+    $user_input = $form_state->getUserInput();
+    NestedArray::setValue($user_input, $parent_element['#parents'], $input);
+    $form_state->setUserInput($user_input);
+    static::setWidgetState($parents, $field_name, $form_state, $field_state);
+    $form_state->setRebuild();
+}
+
+  /**
+   * Ajax refresh callback for the "Remove" button.
+   *
+   * This returns the new page content to replace the page content made obsolete
+   * by the form submission.
+   */
+  public static function removeAjaxContentRefresh(array &$form, FormStateInterface $form_state) {
+    $button = $form_state->getTriggeringElement();
+    return NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -4));
+  }
+
   /**
    * Generates the form element for a single copy of the widget.
    */
