diff --git a/core/core.libraries.yml b/core/core.libraries.yml
index 3d67bf6acc..39e8c8abb8 100644
--- a/core/core.libraries.yml
+++ b/core/core.libraries.yml
@@ -214,6 +214,20 @@ drupal.dropbutton:
     - core/drupalSettings
     - core/jquery.once
 
+drupal.splitbutton:
+  version: VERSION
+  js:
+    misc/splitbutton/splitbutton.js: {}
+  css:
+    component:
+      misc/splitbutton/splitbutton.css: {}
+  dependencies:
+    - core/jquery
+    - core/drupal
+    - core/drupalSettings
+    - core/jquery.once
+    - core/jquery.ui
+
 drupal.entity-form:
   version: VERSION
   js:
diff --git a/core/includes/theme.inc b/core/includes/theme.inc
index d2d54979b7..6b534d7012 100644
--- a/core/includes/theme.inc
+++ b/core/includes/theme.inc
@@ -1178,6 +1178,87 @@ function template_preprocess_container(&$variables) {
   $variables['attributes'] = $element['#attributes'];
 }
 
+/**
+ * Prepares variables for splitbutton templates.
+ *
+ * Default template: splitbutton.html.twig.
+ *
+ * @param array $variables
+ *   An associative array containing:
+ *   - element: An associative array containing the properties of the element.
+ *     Properties used: #id, #attributes.
+ */
+function template_preprocess_splitbutton(array &$variables) {
+  $element = $variables['element'];
+  $element += [
+    '#attributes' => [],
+    '#content_attributes' => [],
+    '#splitbutton_type' => [],
+  ];
+
+  // Attach the library and the required CSS classes.
+  $variables['#attached']['library'][] = 'core/drupal.splitbutton';
+  $element['#attributes']['class'][] = 'js-splitbutton';
+  $element['#content_attributes']['class'][] = 'js-splitbutton-list';
+
+  if (empty($element['#content_attributes']['id'])) {
+    $base = isset($variables['element']['#id']) ? $variables['element']['#id'] . '-' : '';
+    $element['#content_attributes']['id'] = Html::getUniqueId($base . 'splitbutton-list');
+  }
+
+  if (!is_array($element['#splitbutton_type'])) {
+    $element['#splitbutton_type'] = [$element['#splitbutton_type']];
+  }
+  $variables['splitbutton_type'] = $element['#splitbutton_type'];
+
+  foreach (Element::children($element) as $key) {
+    $group = !isset($variables['main_items']) ? 'main_items' : 'items';
+    if (isset($variables['element'][$key]['#main_item'])) {
+      $group = 'main_items';
+    }
+    $variables[$group][$key]['value'] = $variables['element'][$key];
+    $variables[$group][$key]['attributes'] = new Attribute(['role' => 'none']);
+    $variables[$group][$key]['value']['#attributes']['class'][] = 'splitbutton__action';
+
+    unset($variables[$group][$key]['value']['#markup']);
+    unset($variables[$group][$key]['value']['#children']);
+    unset($variables[$group][$key]['value']['#printed']);
+  }
+
+  foreach ($variables['main_items'] as &$main_item) {
+    $main_item['value']['#attributes']['class'][] = 'splitbutton__action--main';
+  }
+
+  foreach ($variables['items'] as &$item) {
+    $item['value']['#attributes']['role'] = 'menuitem';
+    $item['value']['#attributes']['class'][] = 'splitbutton__action--secondary';
+    $item['attributes']->setAttribute('role', 'none');
+  }
+
+  $variables['multiple'] = count($variables['items']) > 0;
+
+  // Special handling for form elements.
+  if (isset($element['#array_parents'])) {
+    // Assign an html ID.
+    if (!isset($element['#attributes']['id'])) {
+      $element['#attributes']['id'] = $element['#id'];
+    }
+  }
+
+  $list_id = $element['#content_attributes']['id'];
+  $button_id = Html::getUniqueId($list_id . '-toggle');
+  $variables['#attached']['drupalSettings']['splitbutton']['instances'][$list_id] = [
+    'buttonId' => $button_id,
+  ];
+
+  $variables['attributes'] = $element['#attributes'];
+  $variables['content_attributes'] = [
+    'role' => 'menu',
+    'aria-labelledby' => $button_id,
+  ] + $element['#content_attributes'];
+
+}
+
 /**
  * Prepares variables for maintenance task list templates.
  *
@@ -1882,6 +1963,9 @@ function drupal_common_theme() {
     'container' => [
       'render element' => 'element',
     ],
+    'splitbutton' => [
+      'render element' => 'element',
+    ],
     // From field system.
     'field' => [
       'render element' => 'element',
diff --git a/core/lib/Drupal/Core/Render/Element/Splitbutton.php b/core/lib/Drupal/Core/Render/Element/Splitbutton.php
new file mode 100644
index 0000000000..35892a92fc
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/Element/Splitbutton.php
@@ -0,0 +1,162 @@
+<?php
+
+namespace Drupal\Core\Render\Element;
+
+use Drupal\Core\Render\Element;
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Provides a render element for a set of links rendered as a drop-down button.
+ *
+ * By default, this element sets #theme so that the 'links' theme hook is used
+ * for rendering, with suffixes so that themes can override this specifically
+ * without overriding all links theming. If the #subtype property is provided in
+ * your render array with value 'foo', #theme is set to links__dropbutton__foo;
+ * if not, it's links__dropbutton; both of these can be overridden by setting
+ * the #theme property in your render array. See template_preprocess_links()
+ * for documentation on the other properties used in theming; for instance, use
+ * element property #links to provide $variables['links'] for theming.
+ *
+ * Properties:
+ * - #links: An array of links to actions. See template_preprocess_links() for
+ *   documentation the properties of links in this array.
+ * - #splitbutton_type: A string ot an array of strings defining types of
+ *   splitbutton variant for styling proposes. Renders as class
+ *  `splitbutton--#splitbutton_type`.
+ *
+ * @see \Drupal\Core\Render\Element\Operations
+ *
+ * @RenderElement("splitbutton")
+ */
+class Splitbutton extends RenderElement {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getInfo() {
+    $class = get_class($this);
+    return [
+      '#process' => [
+        [$class, 'processSplitbutton'],
+        [$class, 'processLinks'],
+        [$class, 'processButtons'],
+      ],
+      '#pre_render' => [
+        [$class, 'preRenderSplitbutton'],
+      ],
+    ];
+  }
+
+  /**
+   * Process Splitbutton items.
+   */
+  public static function processSplitbutton(&$element, FormStateInterface $form_state, &$complete_form) {
+    if (isset($element['#links']) && is_array($element['#links'])) {
+      foreach ($element['#links'] as $key => $value) {
+        $element[$key] = $value;
+      }
+    }
+    unset($element['#links']);
+
+    foreach (Element::children($element) as $key) {
+      $supported_types = ['submit', 'button', 'link'];
+      $child_has_supported_type = isset($element[$key]['#type']) && in_array($element[$key]['#type'], $supported_types);
+      $child_is_legacy_link = isset($element[$key]['title']);
+
+      if (!$child_has_supported_type && !$child_is_legacy_link) {
+        unset($element[$key]);
+      }
+    }
+
+    return $element;
+  }
+
+  /**
+   * Process link elements.
+   */
+  public static function processLinks(&$element, FormStateInterface $form_state, &$complete_form) {
+    foreach (Element::children($element) as $key) {
+      if (isset($element[$key]['title'])) {
+        // This was an old-scool and properly used dropbutton list item.
+        $links_item = $element[$key];
+        $links_item += [
+          'ajax' => NULL,
+          'url' => NULL,
+        ];
+
+        $keys = ['title', 'url'];
+        $link_element = [
+          '#type' => 'link',
+          '#title' => $links_item['title'],
+          '#options' => array_diff_key($links_item, array_combine($keys, $keys)),
+          '#url' => $links_item['url'],
+          '#ajax' => $links_item['ajax'],
+        ];
+
+        // Add the item to the list of links.
+        $element[$key] = !empty($links_item['url']) ? $link_element : ['#markup' => $links_item['title']];
+      }
+    }
+
+    return $element;
+  }
+
+  /**
+   * Processes button elements.
+   */
+  public static function processButtons(&$element, FormStateInterface $form_state, &$complete_form) {
+    foreach (Element::children($element) as $key) {
+      if (
+        !isset($element[$key]['#type']) ||
+        !in_array($element[$key]['#type'], ['submit', 'button'])
+      ) {
+        continue;
+      }
+
+      // If this is a button intentionally allowing incomplete form submission
+      // (e.g., a "Previous" or "Add another item" button), then also skip
+      // client-side validation.
+      if (
+        isset($element[$key]['#limit_validation_errors']) &&
+        $element[$key]['#limit_validation_errors'] !== FALSE
+      ) {
+        $element[$key]['#attributes']['formnovalidate'] = 'formnovalidate';
+      }
+
+      // Add missing id.
+      if (
+        empty($element[$key]['#id']) &&
+        !empty($element[$key]['#name'])
+      ) {
+        $element[$key]['#id'] = $element[$key]['#name'];
+      }
+
+      if (isset($element['#array_parents'])) {
+        $element[$key]['#parents'] = $element['#parents'];
+        $element[$key]['#parents'][] = $key;
+        $element[$key]['#array_parents'] = $element['#array_parents'];
+        $element[$key]['#array_parents'][] = $key;
+        $element[$key] = self::processAjaxForm($element[$key], $form_state, $complete_form);
+
+        $buttons = $form_state->getButtons();
+        $buttons[] = $element[$key];
+        $form_state->setButtons($buttons);
+      }
+    }
+
+    return $element;
+  }
+
+  /**
+   * Pre-render callback: Adds splitbutton theme wrapper.
+   */
+  public static function preRenderSplitbutton($element) {
+    if (!isset($element['#theme_wrappers'])) {
+      $element['#theme_wrappers'] = [];
+    }
+    array_unshift($element['#theme_wrappers'], 'splitbutton');
+
+    return $element;
+  }
+
+}
diff --git a/core/misc/splitbutton/splitbutton.css b/core/misc/splitbutton/splitbutton.css
new file mode 100644
index 0000000000..411301b33b
--- /dev/null
+++ b/core/misc/splitbutton/splitbutton.css
@@ -0,0 +1,124 @@
+
+/**
+ * @file
+ * Base styles for splitbuttons.
+ */
+
+/**
+ * When a splitbutton has only one item, it is simply a button.
+ */
+.splitbutton {
+  display: inline-block;
+  box-sizing: border-box;
+  max-width: 100%;
+}
+
+.splitbutton--multiple {
+  padding-right: 2em; /* LTR */
+}
+
+[dir="rtl"] .splitbutton--multiple {
+  padding-right: 0;
+  padding-left: 2em;
+}
+
+.js .splitbutton--multiple {
+  position: relative;
+}
+
+.splitbutton__action.splitbutton__action {
+  width: 100%;
+  margin: 0;
+  text-align: left; /* LTR */
+}
+[dir="rtl"] .splitbutton__action.splitbutton__action {
+  text-align: right;
+}
+
+@media screen and (max-width: 600px) {
+  .js .splitbutton {
+    width: 100%;
+  }
+}
+
+.js td .splitbutton-multiple .splitbutton-action a,
+.js td .splitbutton-multiple .splitbutton-action input,
+.js td .splitbutton-multiple .splitbutton-action button {
+  width: auto;
+}
+
+/* UL styles are over-scoped in core, so this selector needs weight parity. */
+.splitbutton__list.splitbutton__list {
+  margin: 0;
+  padding: 0;
+  list-style: none;
+}
+
+.js .splitbutton__list .splitbutton__action {
+  display: none;
+}
+
+.splitbutton__list.open .splitbutton__action {
+  display: block;
+}
+
+/**
+ * The splitbutton styling.
+ *
+ * A splitbutton is a widget that displays a list of action links as a button
+ * with a primary action. Secondary actions are hidden behind a click on a
+ * twisty arrow.
+ *
+ * The arrow is created using border on a zero-width, zero-height span.
+ * The arrow inherits the link color, but can be overridden with border colors.
+ */
+.splitbutton.open {
+  z-index: 100;
+  max-width: none;
+}
+
+.splitbutton__toggle.splitbutton__toggle {
+  position: absolute;
+  top: 0;
+  right: 0; /* LTR */
+  bottom: 0;
+  width: 2em;
+  margin: 0;
+  padding: 0;
+}
+[dir="rtl"] .splitbutton__toggle.splitbutton__toggle {
+  right: auto;
+  left: 0;
+}
+
+.splitbutton__toggle-arrow {
+  position: absolute;
+  top: 50%;
+  left: 50%;
+  width: 0;
+  height: 0;
+  transform: translate(-50%, -50%);
+  border-width: 0.3333em 0.3333em 0;
+  border-style: solid;
+  border-right-color: transparent;
+  border-bottom-color: transparent;
+  border-left-color: transparent;
+  line-height: 0;
+}
+
+.splitbutton.open .splitbutton__toggle-arrow {
+  border-top-color: transparent;
+  border-bottom: 0.3333em solid;
+}
+
+.splitbutton .ajax-progress-throbber {
+  position: absolute;
+  top: 100%;
+  left: 0;
+  display: flex;
+  width: auto;
+}
+
+.splitbutton .ajax-progress-throbber .message {
+  flex: 1 0 auto;
+}
diff --git a/core/misc/splitbutton/splitbutton.es6.js b/core/misc/splitbutton/splitbutton.es6.js
new file mode 100644
index 0000000000..51290e1adb
--- /dev/null
+++ b/core/misc/splitbutton/splitbutton.es6.js
@@ -0,0 +1,397 @@
+/**
+ * @file
+ * SplitButton feature.
+ */
+
+(($, Drupal) => {
+  /**
+   * A SplitButton is an HTML list and at least one item as primary action.
+   *
+   * All secondary actions beyond the first in the list are presented in a
+   * dropdown list accessible through a toggle arrow associated with the button.
+   *
+   * @constructor Drupal.SplitButton
+   *
+   * @param {HTMLElement} splitbuttonList
+   *   A DOM element.
+   * @param {object} settings
+   *   A list of options including:
+   * @param {string} settings.title
+   *   The text inside the toggle link element. This text is hidden
+   *   from visual UAs.
+   */
+  function SplitButton(splitbuttonList, settings) {
+    // Merge defaults with settings.
+    const options = $.extend(
+      {
+        title: Drupal.t('List additional actions'),
+      },
+      settings,
+    );
+    const _self = this;
+    const $splitbuttonList = $(splitbuttonList);
+
+    this.expanded = false;
+
+    /**
+     * @type {jQuery}
+     */
+    this.$list = $splitbuttonList;
+
+    /**
+     * @type {jQuery}
+     */
+    this.$splitbutton = $splitbuttonList.closest('.js-splitbutton');
+
+    /**
+     * Secondary actions.
+     *
+     * @type {jQuery}
+     */
+    this.$actions = this.$list.children();
+
+    // Add toggle link.
+    this.$toggle = $(Drupal.theme('splitbuttonToggle', options))
+      .addClass('js-splitbutton-toggle')
+      .attr('aria-haspopup', 'true')
+      .attr('aria-controls', splitbuttonList.id)
+      .attr('id', settings.instances[splitbuttonList.id].buttonId)
+      .on({
+        click: {
+          data: _self,
+          handler: _self.toggleClickHandler,
+        },
+        keydown: {
+          data: _self,
+          handler: _self.toggleKeyHandler,
+        },
+      });
+
+    this.$list.on('keydown', this, this.listKeyHandler);
+
+    if (
+      this.$splitbutton.find('.js-splitbutton-toggle-placeholder').length === 1
+    ) {
+      this.$splitbutton
+        .find('.js-splitbutton-toggle-placeholder')
+        .replaceWith(this.$toggle);
+    } else {
+      this.$list.before(this.$toggle);
+    }
+
+    // Bind mouse events.
+    this.$splitbutton.on({
+      /**
+       * Adds a timeout to close the dropdown on mouseleave.
+       *
+       * @ignore
+       */
+      'mouseleave.splitbutton': $.proxy(this.hoverOut, this),
+
+      /**
+       * Clears timeout when mouseout of the dropdown.
+       *
+       * @ignore
+       */
+      'mouseenter.splitbutton': $.proxy(this.hoverIn, this),
+
+      /**
+       * Similar to mouseleave/mouseenter, but for keyboard navigation.
+       *
+       * @ignore
+       */
+      'focusout.splitbutton': $.proxy(this.focusOut, this),
+
+      /**
+       * @ignore
+       */
+      'focusin.splitbutton': $.proxy(this.focusIn, this),
+    });
+  }
+
+  /**
+   * Process elements with the .js-splitbutton class on page load.
+   *
+   * @type {Drupal~behavior}
+   *
+   * @prop {Drupal~behaviorAttach} attach
+   *   Attaches splitButton behaviors.
+   */
+  Drupal.behaviors.splitButton = {
+    attach(context, settings) {
+      const $splitbuttonLists = $(context)
+        .find('.js-splitbutton-list')
+        .once('splitbutton');
+
+      if ($splitbuttonLists.length) {
+        // Initialize all buttons.
+        const il = $splitbuttonLists.length;
+        for (let i = 0; i < il; i++) {
+          SplitButton.splitbuttons.push(
+            new SplitButton($splitbuttonLists[i], settings.splitbutton),
+          );
+        }
+      }
+    },
+  };
+
+  /**
+   * Extend the SplitButton constructor.
+   */
+  $.extend(
+    SplitButton,
+    /** @lends Drupal.SplitButton */ {
+      /**
+       * Store all processed SplitButtons.
+       *
+       * @type {Array.<Drupal.SplitButton>}
+       */
+      splitbuttons: [],
+    },
+  );
+
+  /**
+   * Extend the SplitButton prototype.
+   */
+  $.extend(
+    SplitButton.prototype,
+    /** @lends Drupal.SplitButton# */ {
+      /**
+       * Toggle the splitbutton open and closed.
+       *
+       * @param {bool} [show]
+       *   Force the splitbutton to open by passing true or to close by
+       *   passing false.
+       *
+       * @return {Drupal.SplitButton}
+       *   The SplitButton instance.
+       */
+      toggle(show) {
+        const isBool = typeof show === 'boolean';
+        show = isBool ? show : !this.expanded;
+        this.expanded = show;
+        this.$list.toggleClass('open', show);
+        this.$splitbutton.toggleClass('open', show);
+
+        if (show) {
+          this.$toggle.attr('aria-expanded', 'true');
+        } else {
+          this.$toggle.removeAttr('aria-expanded');
+        }
+
+        return this;
+      },
+
+      /**
+       * Move action focus to the specified direction.
+       *
+       * @param {string} direction
+       *   Can be 'previous', 'next', 'first' or 'last'.
+       */
+      focusAction(direction) {
+        const $listItemFocused = this.$actions.has(':focus');
+        const $listItemFirst = this.$actions.first();
+        const $listItemLast = this.$actions.last();
+        const $nextlistItem = $listItemFocused.next().length
+          ? $listItemFocused.next()
+          : $listItemLast;
+        const $prevlistItem = $listItemFocused.prev().length
+          ? $listItemFocused.prev()
+          : $listItemFirst;
+
+        this.toggle(true);
+
+        /* eslint-disable default-case */
+        switch (direction) {
+          case 'next':
+            $nextlistItem
+              .find(':focusable')
+              .first()
+              .trigger('focus');
+            break;
+
+          case 'previous':
+            $prevlistItem
+              .find(':focusable')
+              .first()
+              .trigger('focus');
+            break;
+
+          case 'first':
+            $listItemFirst
+              .find(':focusable')
+              .first()
+              .trigger('focus');
+            break;
+
+          case 'last':
+            $listItemLast
+              .find(':focusable')
+              .first()
+              .trigger('focus');
+            break;
+        }
+        /* eslint-enable default-case */
+      },
+
+      /**
+       * Delegated callback for toggling secondary splitbutton actions.
+       *
+       * This is an event handler attached to the toggle button.
+       *
+       * @param {jQuery.Event} event
+       *   The event triggered.
+       * @param {Drupal.SplitButton} event.data
+       *   The actual SplitButton instance.
+       */
+      toggleClickHandler(event) {
+        event.data.toggle();
+        event.preventDefault();
+      },
+
+      /**
+       * Delegated callback for toggling secondary splitbutton actions.
+       *
+       * This is an event handler attached to the toggle button.
+       *
+       * @param {jQuery.Event} event
+       *   The event triggered.
+       * @param {Drupal.SplitButton} event.data
+       *   The actual SplitButton instance.
+       */
+      toggleKeyHandler(event) {
+        /* eslint-disable default-case */
+        switch (event.key) {
+          case 'ArrowUp':
+            event.data.focusAction('last');
+            event.preventDefault();
+            break;
+
+          case ' ':
+          case 'Enter':
+            event.data.toggle();
+            event.preventDefault();
+            break;
+
+          case 'ArrowDown':
+            event.data.focusAction('first');
+            event.preventDefault();
+            break;
+        }
+        /* eslint-enable default-case */
+      },
+
+      /**
+       * Delegated callback for toggling secondary splitbutton actions.
+       *
+       * This is an event handler attached to the toggle button.
+       *
+       * @param {jQuery.Event} event
+       *   The event triggered.
+       * @param {Drupal.SplitButton} event.data
+       *   The actual SplitButton instance.
+       */
+      listKeyHandler(event) {
+        /* eslint-disable default-case */
+        switch (event.key) {
+          case 'ArrowUp':
+            event.data.focusAction('previous');
+            event.preventDefault();
+            break;
+
+          case 'ArrowDown':
+            event.data.focusAction('next');
+            event.preventDefault();
+            break;
+
+          case 'Home':
+            event.data.focusAction('first');
+            event.preventDefault();
+            break;
+
+          case 'End':
+            event.data.focusAction('last');
+            event.preventDefault();
+            break;
+
+          case 'Escape':
+            event.data.toggle(false).$toggle.trigger('focus');
+            event.preventDefault();
+            break;
+        }
+        /* eslint-enable default-case */
+      },
+
+      /**
+       * @method
+       */
+      hoverIn() {
+        // Clear any previous timer we were using.
+        if (this.timerID) {
+          window.clearTimeout(this.timerID);
+        }
+      },
+
+      /**
+       * @method
+       */
+      hoverOut() {
+        // Wait half a second before closing.
+        this.timerID = window.setTimeout($.proxy(this, 'close'), 500);
+      },
+
+      /**
+       * @method
+       */
+      open() {
+        this.toggle(true);
+      },
+
+      /**
+       * @method
+       */
+      close() {
+        this.toggle(false);
+      },
+
+      /**
+       * @param {jQuery.Event} e
+       *   The event triggered.
+       */
+      focusOut(e) {
+        this.hoverOut.call(this, e);
+      },
+
+      /**
+       * @param {jQuery.Event} e
+       *   The event triggered.
+       */
+      focusIn(e) {
+        this.hoverIn.call(this, e);
+      },
+    },
+  );
+
+  $.extend(
+    Drupal.theme,
+    /** @lends Drupal.theme */ {
+      /**
+       * A toggle is an interactive element often bound to a click handler.
+       *
+       * @param {object} options
+       *   Options object.
+       * @param {string} [options.title]
+       *   The button text.
+       *
+       * @return {string}
+       *   A string representing a DOM fragment.
+       */
+      splitbuttonToggle(options) {
+        return `<button type="button" class="button splitbutton__toggle"><span class="splitbutton__toggle-arrow"></span>&nbsp;<span class="visually-hidden">${options.title}</span></button>`;
+      },
+    },
+  );
+
+  // Expose constructor in the public space.
+  Drupal.SplitButton = SplitButton;
+})(jQuery, Drupal);
diff --git a/core/misc/splitbutton/splitbutton.js b/core/misc/splitbutton/splitbutton.js
new file mode 100644
index 0000000000..8accec0bc4
--- /dev/null
+++ b/core/misc/splitbutton/splitbutton.js
@@ -0,0 +1,194 @@
+/**
+* DO NOT EDIT THIS FILE.
+* See the following change record for more information,
+* https://www.drupal.org/node/2815083
+* @preserve
+**/
+
+(function ($, Drupal) {
+  function SplitButton(splitbuttonList, settings) {
+    var options = $.extend({
+      title: Drupal.t('List additional actions')
+    }, settings);
+    var _self = this;
+    var $splitbuttonList = $(splitbuttonList);
+
+    this.expanded = false;
+
+    this.$list = $splitbuttonList;
+
+    this.$splitbutton = $splitbuttonList.closest('.js-splitbutton');
+
+    this.$actions = this.$list.children();
+
+    this.$toggle = $(Drupal.theme('splitbuttonToggle', options)).addClass('js-splitbutton-toggle').attr('aria-haspopup', 'true').attr('aria-controls', splitbuttonList.id).attr('id', settings.instances[splitbuttonList.id].buttonId).on({
+      click: {
+        data: _self,
+        handler: _self.toggleClickHandler
+      },
+      keydown: {
+        data: _self,
+        handler: _self.toggleKeyHandler
+      }
+    });
+
+    this.$list.on('keydown', this, this.listKeyHandler);
+
+    if (this.$splitbutton.find('.js-splitbutton-toggle-placeholder').length === 1) {
+      this.$splitbutton.find('.js-splitbutton-toggle-placeholder').replaceWith(this.$toggle);
+    } else {
+      this.$list.before(this.$toggle);
+    }
+
+    this.$splitbutton.on({
+      'mouseleave.splitbutton': $.proxy(this.hoverOut, this),
+
+      'mouseenter.splitbutton': $.proxy(this.hoverIn, this),
+
+      'focusout.splitbutton': $.proxy(this.focusOut, this),
+
+      'focusin.splitbutton': $.proxy(this.focusIn, this)
+    });
+  }
+
+  Drupal.behaviors.splitButton = {
+    attach: function attach(context, settings) {
+      var $splitbuttonLists = $(context).find('.js-splitbutton-list').once('splitbutton');
+
+      if ($splitbuttonLists.length) {
+        var il = $splitbuttonLists.length;
+        for (var i = 0; i < il; i++) {
+          SplitButton.splitbuttons.push(new SplitButton($splitbuttonLists[i], settings.splitbutton));
+        }
+      }
+    }
+  };
+
+  $.extend(SplitButton, {
+    splitbuttons: []
+  });
+
+  $.extend(SplitButton.prototype, {
+    toggle: function toggle(show) {
+      var isBool = typeof show === 'boolean';
+      show = isBool ? show : !this.expanded;
+      this.expanded = show;
+      this.$list.toggleClass('open', show);
+      this.$splitbutton.toggleClass('open', show);
+
+      if (show) {
+        this.$toggle.attr('aria-expanded', 'true');
+      } else {
+        this.$toggle.removeAttr('aria-expanded');
+      }
+
+      return this;
+    },
+    focusAction: function focusAction(direction) {
+      var $listItemFocused = this.$actions.has(':focus');
+      var $listItemFirst = this.$actions.first();
+      var $listItemLast = this.$actions.last();
+      var $nextlistItem = $listItemFocused.next().length ? $listItemFocused.next() : $listItemLast;
+      var $prevlistItem = $listItemFocused.prev().length ? $listItemFocused.prev() : $listItemFirst;
+
+      this.toggle(true);
+
+      switch (direction) {
+        case 'next':
+          $nextlistItem.find(':focusable').first().trigger('focus');
+          break;
+
+        case 'previous':
+          $prevlistItem.find(':focusable').first().trigger('focus');
+          break;
+
+        case 'first':
+          $listItemFirst.find(':focusable').first().trigger('focus');
+          break;
+
+        case 'last':
+          $listItemLast.find(':focusable').first().trigger('focus');
+          break;
+      }
+    },
+    toggleClickHandler: function toggleClickHandler(event) {
+      event.data.toggle();
+      event.preventDefault();
+    },
+    toggleKeyHandler: function toggleKeyHandler(event) {
+      switch (event.key) {
+        case 'ArrowUp':
+          event.data.focusAction('last');
+          event.preventDefault();
+          break;
+
+        case ' ':
+        case 'Enter':
+          event.data.toggle();
+          event.preventDefault();
+          break;
+
+        case 'ArrowDown':
+          event.data.focusAction('first');
+          event.preventDefault();
+          break;
+      }
+    },
+    listKeyHandler: function listKeyHandler(event) {
+      switch (event.key) {
+        case 'ArrowUp':
+          event.data.focusAction('previous');
+          event.preventDefault();
+          break;
+
+        case 'ArrowDown':
+          event.data.focusAction('next');
+          event.preventDefault();
+          break;
+
+        case 'Home':
+          event.data.focusAction('first');
+          event.preventDefault();
+          break;
+
+        case 'End':
+          event.data.focusAction('last');
+          event.preventDefault();
+          break;
+
+        case 'Escape':
+          event.data.toggle(false).$toggle.trigger('focus');
+          event.preventDefault();
+          break;
+      }
+    },
+    hoverIn: function hoverIn() {
+      if (this.timerID) {
+        window.clearTimeout(this.timerID);
+      }
+    },
+    hoverOut: function hoverOut() {
+      this.timerID = window.setTimeout($.proxy(this, 'close'), 500);
+    },
+    open: function open() {
+      this.toggle(true);
+    },
+    close: function close() {
+      this.toggle(false);
+    },
+    focusOut: function focusOut(e) {
+      this.hoverOut.call(this, e);
+    },
+    focusIn: function focusIn(e) {
+      this.hoverIn.call(this, e);
+    }
+  });
+
+  $.extend(Drupal.theme, {
+    splitbuttonToggle: function splitbuttonToggle(options) {
+      return '<button type="button" class="button splitbutton__toggle"><span class="splitbutton__toggle-arrow"></span>&nbsp;<span class="visually-hidden">' + options.title + '</span></button>';
+    }
+  });
+
+  Drupal.SplitButton = SplitButton;
+})(jQuery, Drupal);
\ No newline at end of file
diff --git a/core/modules/system/templates/splitbutton.html.twig b/core/modules/system/templates/splitbutton.html.twig
new file mode 100644
index 0000000000..400e40e4c9
--- /dev/null
+++ b/core/modules/system/templates/splitbutton.html.twig
@@ -0,0 +1,41 @@
+{#
+/**
+ * @file
+ * Default theme implementation of a splitbutton used to wrap child elements.
+ *
+ * Used for grouped buttons and link items.
+ *
+ * Available variables:
+ * - attributes: HTML attributes for the containing element.
+ * - content_attributes: HTML attributes for the list element.
+ * - item_attributes: HTML attributes for the list item.
+ * - main_items: The uncollapsed splitbutton elements.
+ * - items: Further child elements of the splitbutton.
+ * - multiple: Whether the splitbutton has more than one item.
+ *
+ * @see template_preprocess_splitbutton()
+ */
+#}
+{% if main_item or items %}
+{%
+  set classes = [
+    'splitbutton',
+    multiple ? 'splitbutton--multiple' : 'splitbutton--single'
+  ]
+%}
+<div{{ attributes.addClass(classes) }}>
+  <div class="splitbutton__main-items">
+  {% for main_item in main_items %}
+    {{ main_item.value }}
+  {% endfor %}
+  </div>
+
+  {% if items %}
+  <ul{{ content_attributes.addClass('splitbutton__list') }}>
+    {% for item in items %}
+      <li{{ item.attributes.addClass('splitbutton__list-item') }}>{{ item.value }}</li>
+    {% endfor %}
+  </ul>
+  {% endif %}
+</div>
+{% endif %}
diff --git a/core/themes/claro/claro.theme b/core/themes/claro/claro.theme
index 8f35b25ed0..ebf4772639 100644
--- a/core/themes/claro/claro.theme
+++ b/core/themes/claro/claro.theme
@@ -823,7 +823,7 @@ function claro_preprocess_field_multiple_value_form(&$variables) {
     }
 
     // Make add-more button smaller.
-    if (!empty($variables['button'])) {
+    if (isset($variables['button']['#type']) && $variables['button']['#type'] == 'submit') {
       $variables['button']['#attributes']['class'][] = 'button--small';
     }
   }
diff --git a/core/themes/stable/css/core/splitbutton/splitbutton.css b/core/themes/stable/css/core/splitbutton/splitbutton.css
new file mode 100644
index 0000000000..411301b33b
--- /dev/null
+++ b/core/themes/stable/css/core/splitbutton/splitbutton.css
@@ -0,0 +1,124 @@
+
+/**
+ * @file
+ * Base styles for splitbuttons.
+ */
+
+/**
+ * When a splitbutton has only one item, it is simply a button.
+ */
+.splitbutton {
+  display: inline-block;
+  box-sizing: border-box;
+  max-width: 100%;
+}
+
+.splitbutton--multiple {
+  padding-right: 2em; /* LTR */
+}
+
+[dir="rtl"] .splitbutton--multiple {
+  padding-right: 0;
+  padding-left: 2em;
+}
+
+.js .splitbutton--multiple {
+  position: relative;
+}
+
+.splitbutton__action.splitbutton__action {
+  width: 100%;
+  margin: 0;
+  text-align: left; /* LTR */
+}
+[dir="rtl"] .splitbutton__action.splitbutton__action {
+  text-align: right;
+}
+
+@media screen and (max-width: 600px) {
+  .js .splitbutton {
+    width: 100%;
+  }
+}
+
+.js td .splitbutton-multiple .splitbutton-action a,
+.js td .splitbutton-multiple .splitbutton-action input,
+.js td .splitbutton-multiple .splitbutton-action button {
+  width: auto;
+}
+
+/* UL styles are over-scoped in core, so this selector needs weight parity. */
+.splitbutton__list.splitbutton__list {
+  margin: 0;
+  padding: 0;
+  list-style: none;
+}
+
+.js .splitbutton__list .splitbutton__action {
+  display: none;
+}
+
+.splitbutton__list.open .splitbutton__action {
+  display: block;
+}
+
+/**
+ * The splitbutton styling.
+ *
+ * A splitbutton is a widget that displays a list of action links as a button
+ * with a primary action. Secondary actions are hidden behind a click on a
+ * twisty arrow.
+ *
+ * The arrow is created using border on a zero-width, zero-height span.
+ * The arrow inherits the link color, but can be overridden with border colors.
+ */
+.splitbutton.open {
+  z-index: 100;
+  max-width: none;
+}
+
+.splitbutton__toggle.splitbutton__toggle {
+  position: absolute;
+  top: 0;
+  right: 0; /* LTR */
+  bottom: 0;
+  width: 2em;
+  margin: 0;
+  padding: 0;
+}
+[dir="rtl"] .splitbutton__toggle.splitbutton__toggle {
+  right: auto;
+  left: 0;
+}
+
+.splitbutton__toggle-arrow {
+  position: absolute;
+  top: 50%;
+  left: 50%;
+  width: 0;
+  height: 0;
+  transform: translate(-50%, -50%);
+  border-width: 0.3333em 0.3333em 0;
+  border-style: solid;
+  border-right-color: transparent;
+  border-bottom-color: transparent;
+  border-left-color: transparent;
+  line-height: 0;
+}
+
+.splitbutton.open .splitbutton__toggle-arrow {
+  border-top-color: transparent;
+  border-bottom: 0.3333em solid;
+}
+
+.splitbutton .ajax-progress-throbber {
+  position: absolute;
+  top: 100%;
+  left: 0;
+  display: flex;
+  width: auto;
+}
+
+.splitbutton .ajax-progress-throbber .message {
+  flex: 1 0 auto;
+}
diff --git a/core/themes/stable/stable.info.yml b/core/themes/stable/stable.info.yml
index 0f20f7c604..608db4af6e 100644
--- a/core/themes/stable/stable.info.yml
+++ b/core/themes/stable/stable.info.yml
@@ -85,6 +85,13 @@ libraries-override:
     css:
       component:
         misc/dropbutton/dropbutton.css: css/core/dropbutton/dropbutton.css
+
+  core/drupal.splitbutton:
+    css:
+      component:
+        misc/splitbutton/splitbutton.css: css/core/splitbutton/splitbutton.css
+
+
   core/drupal.vertical-tabs:
     css:
       component:
