diff --git a/core/core.libraries.yml b/core/core.libraries.yml
index 0c212ce..3908c80 100644
--- a/core/core.libraries.yml
+++ b/core/core.libraries.yml
@@ -102,7 +102,7 @@ drupal.autocomplete:
     - core/drupal
     - core/drupalSettings
     - core/drupal.ajax
-    - core/jquery.ui.autocomplete
+    - core/select2
 
 drupal.batch:
   version: VERSION
@@ -808,3 +808,19 @@ underscore:
     gpl-compatible: true
   js:
     assets/vendor/underscore/underscore.js: { weight: -20 }
+
+select2:
+  remote: https://github.com/ivaynberg/select2
+  version: 3.5.2
+  license:
+    name: GNU-GPL-2.0-or-later
+    url: https://github.com/ivaynberg/select2/blob/master/LICENSE
+    gpl-compatible: true
+  js:
+    assets/vendor/select2/select2.js: {}
+    misc/select2.js: {}
+  css:
+    theme:
+      assets/vendor/select2/select2.css: {}
+  dependencies:
+    - core/jquery
diff --git a/core/lib/Drupal/Core/Installer/Form/SelectLanguageForm.php b/core/lib/Drupal/Core/Installer/Form/SelectLanguageForm.php
index 79d9082..0e67b5e 100644
--- a/core/lib/Drupal/Core/Installer/Form/SelectLanguageForm.php
+++ b/core/lib/Drupal/Core/Installer/Form/SelectLanguageForm.php
@@ -67,6 +67,8 @@ public function buildForm(array $form, FormStateInterface $form_state, $install_
       '#options' => $select_options,
       // Use the browser detected language as default or English if nothing found.
       '#default_value' => !empty($browser_langcode) ? $browser_langcode : 'en',
+      // Use the Select2 library.
+      '#select2' => TRUE,
     );
     $form['help'] = array(
       '#type' => 'item',
diff --git a/core/lib/Drupal/Core/Installer/Form/SiteConfigureForm.php b/core/lib/Drupal/Core/Installer/Form/SiteConfigureForm.php
index 750e967..146ee97 100644
--- a/core/lib/Drupal/Core/Installer/Form/SiteConfigureForm.php
+++ b/core/lib/Drupal/Core/Installer/Form/SiteConfigureForm.php
@@ -199,6 +199,8 @@ public function buildForm(array $form, FormStateInterface $form_state) {
       '#description' => $this->t('By default, dates in this site will be displayed in the chosen time zone.'),
       '#weight' => 5,
       '#attributes' => array('class' => array('timezone-detect')),
+      // Use the Select2 library.
+      '#select2' => TRUE,
     );
 
     $form['update_notifications'] = array(
diff --git a/core/lib/Drupal/Core/Render/Element/FormElement.php b/core/lib/Drupal/Core/Render/Element/FormElement.php
index 401c850..b6d1789 100644
--- a/core/lib/Drupal/Core/Render/Element/FormElement.php
+++ b/core/lib/Drupal/Core/Render/Element/FormElement.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\Core\Render\Element;
 
+use Drupal\Component\Serialization\Json;
 use Drupal\Core\Form\FormStateInterface;
 
 /**
@@ -123,6 +124,49 @@ public static function processAutocomplete(&$element, FormStateInterface $form_s
       $element['#attached']['library'][] = 'core/drupal.autocomplete';
       // Provide a data attribute for the JavaScript behavior to bind to.
       $element['#attributes']['data-autocomplete-path'] = $path;
+
+      // Provide a data attribute which stores extra Select2 configuration.
+      $options = isset($element['#autocomplete_options']) ? $element['#autocomplete_options'] : array();
+      $element['#attributes']['data-autocomplete-options'] = Json::encode($options);
+    }
+
+    return $element;
+  }
+
+  /**
+   * Integrates form elements with the Select2 JavaScript library.
+   *
+   * @param array $element
+   *   The form element to process. Properties used:
+   *   - #select2: Whether to use the Select2 library for this form element. If
+   *     the value is TRUE, the Select2 library is initialized with its default
+   *     options. If the value is an array, each key and value will map to the
+   *     corresponding Select2 configuration.
+   *     @see http://ivaynberg.github.io/select2/#documentation for the list of
+   *     possible configuration options.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   * @param array $complete_form
+   *   The complete form structure.
+   *
+   * @return array
+   *   The form element.
+   */
+  public static function processSelect2(&$element, FormStateInterface $form_state, &$complete_form) {
+    if (!empty($element['#select2'])) {
+      // Provide some default options.
+      // @todo Figure out if we need more defaults here.
+      $configuration = array(
+        'width' => 'resolve',
+      );
+      if (is_array($element['#select2'])) {
+        $configuration = $element['#select2'] + $configuration;
+      }
+
+      $element['#attributes']['class'][] = 'form-select2';
+      $element['#attached']['library'][] = 'core/select2';
+      // Provide a data attribute which stores the Select2 configuration array.
+      $element['#attributes']['data-drupal-select2'] = Json::encode($configuration);
     }
 
     return $element;
diff --git a/core/lib/Drupal/Core/Render/Element/Select.php b/core/lib/Drupal/Core/Render/Element/Select.php
index 70f14d6..4507fdb 100644
--- a/core/lib/Drupal/Core/Render/Element/Select.php
+++ b/core/lib/Drupal/Core/Render/Element/Select.php
@@ -31,6 +31,9 @@ public function getInfo() {
       '#process' => array(
         array($class, 'processSelect'),
         array($class, 'processAjaxForm'),
+        // Not to be confused with the processor above, this one is about the
+        // Select2 Javascript library.
+        array($class, 'processSelect2'),
       ),
       '#pre_render' => array(
         array($class, 'preRenderSelect'),
diff --git a/core/misc/autocomplete.js b/core/misc/autocomplete.js
index af64521..3e5d07a 100644
--- a/core/misc/autocomplete.js
+++ b/core/misc/autocomplete.js
@@ -2,16 +2,14 @@
 
   "use strict";
 
-  var autocomplete;
-
   /**
-   * Helper splitting terms from the autocomplete value.
+   * Helper for splitting terms from the autocomplete value.
    *
    * @param {String} value
    *
    * @return {Array}
    */
-  function autocompleteSplitValues(value) {
+  function splitValues(value) {
     // We will match the value against comma-separated terms.
     var result = [];
     var quote = false;
@@ -41,132 +39,51 @@
   }
 
   /**
-   * Returns the last value of an multi-value textfield.
-   *
-   * @param {String} terms
-   *
-   * @return {String}
+   * Sanitizes and splits the initial value of an autocomplete form element in
+   * the form required by Select2.
    */
-  function extractLastTerm(terms) {
-    return autocomplete.splitValues(terms).pop();
+  function prepareInitialValue(element, callback) {
+    var idInText = element.data('autocompleteOptions')['id_in_text'] || false;
+
+    var data = splitValues(element.val()).map(function (value) {
+      // Strip the identifier from the default value of an autocomplete
+      // element. The value is expected to be in the form "text (id)".
+      var textValue = idInText ? value.match(/"?(.+) \(\d+|[\w.]+\)"?/)[1] : value;
+      return {id: value, text: window.Select2.util.escapeMarkup(textValue.replace('""', '"', 'g'))};
+    });
+    callback(data);
   }
 
-  /**
-   * The search handler is called before a search is performed.
-   *
-   * @param {Object} event
-   *
-   * @return {Boolean}
-   */
-  function searchHandler(event) {
-    // Only search when the term is two characters or larger.
-    var term = autocomplete.extractLastTerm(event.target.value);
-    return term.length >= autocomplete.minLength;
-  }
-
-  /**
-   * jQuery UI autocomplete source callback.
-   *
-   * @param {Object} request
-   * @param {Function} response
-   */
-  function sourceData(request, response) {
-    /*jshint validthis:true */
-    var elementId = this.element.attr('id');
-
-    if (!(elementId in autocomplete.cache)) {
-      autocomplete.cache[elementId] = {};
-    }
-
-    /**
-     * Filter through the suggestions removing all terms already tagged and
-     * display the available terms to the user.
-     *
-     * @param {Object} suggestions
-     */
-    function showSuggestions(suggestions) {
-      var tagged = autocomplete.splitValues(request.term);
-      for (var i = 0, il = tagged.length; i < il; i++) {
-        var index = suggestions.indexOf(tagged[i]);
-        if (index >= 0) {
-          suggestions.splice(index, 1);
-        }
+  // Generic Select2 options that do not depend on any autocomplete element
+  // specific configuration.
+  var select2Options = {
+    tokenSeparators: [","],
+    tags: [],
+    minimumInputLength: 1,
+    cache: true,
+    width: 'resolve',
+    createSearchChoice: function (term) {
+      return {id: term, text: term};
+    },
+    initSelection: prepareInitialValue,
+    ajax: {
+      dataType: 'json',
+      quietMillis: 200,
+      data: function (term, page) {
+        return {q: term};
+      },
+      results: function (data, page) {
+        return {
+          // Transform Drupal autocomplete format to Select2 format.
+          results: data.map(function (result) {
+            return {id: result.value, text: result.label};
+          })
+        };
       }
-      response(suggestions);
-    }
-
-    /**
-     * Transforms the data object into an array and update autocomplete results.
-     *
-     * @param {Object} data
-     */
-    function sourceCallbackHandler(data) {
-      autocomplete.cache[elementId][term] = data;
-
-      // Send the new string array of terms to the jQuery UI list.
-      showSuggestions(data);
-    }
-
-    // Get the desired term and construct the autocomplete URL for it.
-    var term = autocomplete.extractLastTerm(request.term);
-
-    // Check if the term is already cached.
-    if (autocomplete.cache[elementId].hasOwnProperty(term)) {
-      showSuggestions(autocomplete.cache[elementId][term]);
-    }
-    else {
-      var options = $.extend({ success: sourceCallbackHandler, data: { q: term } }, autocomplete.ajax);
-      /*jshint validthis:true */
-      $.ajax(this.element.attr('data-autocomplete-path'), options);
-    }
-  }
-
-  /**
-   * Handles an autocompletefocus event.
-   *
-   * @return {Boolean}
-   */
-  function focusHandler() {
-    return false;
-  }
-
-  /**
-   * Handles an autocompleteselect event.
-   *
-   * @param {Object} event
-   * @param {Object} ui
-   *
-   * @return {Boolean}
-   */
-  function selectHandler(event, ui) {
-    var terms = autocomplete.splitValues(event.target.value);
-    // Remove the current input.
-    terms.pop();
-    // Add the selected item.
-    if (ui.item.value.search(",") > 0) {
-      terms.push('"' + ui.item.value + '"');
-    }
-    else {
-      terms.push(ui.item.value);
-    }
-    event.target.value = terms.join(', ');
-    // Return false to tell jQuery UI that we've filled in the value already.
-    return false;
-  }
-
-  /**
-   * Override jQuery UI _renderItem function to output HTML by default.
-   *
-   * @param {Object} ul
-   * @param {Object} item
-   *
-   * @return {Object}
-   */
-  function renderItem(ul, item) {
-    return $("<li>")
-      .append($("<a>").html(item.label))
-      .appendTo(ul);
-  }
+    },
+    // We do not want to escape markup since we are displaying html.
+    escapeMarkup: function (markup) { return markup; }
+  };
 
   /**
    * Attaches the autocomplete behavior to all required fields.
@@ -174,45 +91,17 @@
   Drupal.behaviors.autocomplete = {
     attach: function (context) {
       // Act on textfields with the "form-autocomplete" class.
-      var $autocomplete = $(context).find('input.form-autocomplete').once('autocomplete');
-      if ($autocomplete.length) {
-        // Use jQuery UI Autocomplete on the textfield.
-        $autocomplete.autocomplete(autocomplete.options)
-          .data("ui-autocomplete")
-          ._renderItem = autocomplete.options.renderItem;
-      }
-    },
-    detach: function (context, settings, trigger) {
-      if (trigger === 'unload') {
-        $(context).find('input.form-autocomplete')
-          .removeOnce('autocomplete')
-          .autocomplete('destroy');
-      }
-    }
-  };
+      $(context).find('input.form-autocomplete').once('autocomplete')
+        .each(function () {
+          // Merge the default options with the user-defined ones.
+          var autocompleteOptions = $.extend({}, select2Options, JSON.parse(this.getAttribute('data-autocomplete-options')));
 
-  /**
-   * Autocomplete object implementation.
-   */
-  autocomplete = {
-    cache: {},
-    // Exposes methods to allow overriding by contrib.
-    minLength: 1,
-    splitValues: autocompleteSplitValues,
-    extractLastTerm: extractLastTerm,
-    // jQuery UI autocomplete options.
-    options: {
-      source: sourceData,
-      focus: focusHandler,
-      search: searchHandler,
-      select: selectHandler,
-      renderItem: renderItem
-    },
-    ajax: {
-      dataType: 'json'
+          // Set the element-specific AJAX url.
+          autocompleteOptions.ajax.url = this.getAttribute('data-autocomplete-path');
+
+          $(this).select2(autocompleteOptions);
+        });
     }
   };
 
-  Drupal.autocomplete = autocomplete;
-
 })(jQuery, Drupal);
diff --git a/core/misc/select2.js b/core/misc/select2.js
new file mode 100644
index 0000000..db320ec
--- /dev/null
+++ b/core/misc/select2.js
@@ -0,0 +1,17 @@
+(function ($, Drupal) {
+
+  "use strict";
+
+  /**
+   * Attaches the Select2 behavior to all form elements that requested it.
+   */
+  Drupal.behaviors.select2 = {
+    attach: function (context) {
+      $(context).find('[data-drupal-select2]').once('drupal-select2')
+        .each(function () {
+          $(this).select2(JSON.parse(this.getAttribute('data-drupal-select2')));
+        });
+    }
+  };
+
+})(jQuery, Drupal);
diff --git a/core/modules/entity_reference/src/EntityReferenceAutocomplete.php b/core/modules/entity_reference/src/EntityReferenceAutocomplete.php
index ffc9995..dcb213a 100644
--- a/core/modules/entity_reference/src/EntityReferenceAutocomplete.php
+++ b/core/modules/entity_reference/src/EntityReferenceAutocomplete.php
@@ -62,8 +62,6 @@ public function __construct(EntityManagerInterface $entity_manager, SelectionPlu
    * @param string $entity_id
    *   (optional) The entity ID the entity reference field is attached to.
    *   Defaults to ''.
-   * @param string $prefix
-   *   (optional) A prefix for all the keys returned by this function.
    * @param string $string
    *   (optional) The label of the entity to query by.
    *
@@ -75,7 +73,7 @@ public function __construct(EntityManagerInterface $entity_manager, SelectionPlu
    *
    * @see \Drupal\entity_reference\EntityReferenceController
    */
-  public function getMatches(FieldDefinitionInterface $field_definition, $entity_type, $bundle, $entity_id = '', $prefix = '', $string = '') {
+  public function getMatches(FieldDefinitionInterface $field_definition, $entity_type, $bundle, $entity_id = '', $string = '') {
     $matches = array();
     $entity = NULL;
 
@@ -102,7 +100,7 @@ public function getMatches(FieldDefinitionInterface $field_definition, $entity_t
           $key = preg_replace('/\s\s+/', ' ', str_replace("\n", '', trim(String::decodeEntities(strip_tags($key)))));
           // Names containing commas or quotes must be wrapped in quotes.
           $key = Tags::encode($key);
-          $matches[] = array('value' => $prefix . $key, 'label' => $label);
+          $matches[] = array('value' => $key, 'label' => $label);
         }
       }
     }
diff --git a/core/modules/entity_reference/src/EntityReferenceController.php b/core/modules/entity_reference/src/EntityReferenceController.php
index afc4cc0..c83d74c 100644
--- a/core/modules/entity_reference/src/EntityReferenceController.php
+++ b/core/modules/entity_reference/src/EntityReferenceController.php
@@ -86,16 +86,9 @@ public function handleAutocomplete(Request $request, $type, $field_name, $entity
     // Get the typed string, if exists from the URL.
     $items_typed = $request->query->get('q');
     $items_typed = Tags::explode($items_typed);
-    $last_item = Unicode::strtolower(array_pop($items_typed));
+    $typed_string = Unicode::strtolower(array_pop($items_typed));
 
-    $prefix = '';
-    // The user entered a comma-separated list of entity labels, so we generate
-    // a prefix.
-    if ($type == 'tags' && !empty($last_item)) {
-      $prefix = count($items_typed) ? Tags::implode($items_typed) . ', ' : '';
-    }
-
-    $matches = $this->entityReferenceAutocomplete->getMatches($field_definition, $entity_type, $bundle_name, $entity_id, $prefix, $last_item);
+    $matches = $this->entityReferenceAutocomplete->getMatches($field_definition, $entity_type, $bundle_name, $entity_id, $typed_string);
 
     return new JsonResponse($matches);
   }
diff --git a/core/modules/entity_reference/src/Plugin/Field/FieldWidget/AutocompleteWidgetBase.php b/core/modules/entity_reference/src/Plugin/Field/FieldWidget/AutocompleteWidgetBase.php
index 40d0aaf..b392f45 100644
--- a/core/modules/entity_reference/src/Plugin/Field/FieldWidget/AutocompleteWidgetBase.php
+++ b/core/modules/entity_reference/src/Plugin/Field/FieldWidget/AutocompleteWidgetBase.php
@@ -93,6 +93,9 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen
       '#default_value' => implode(', ', $this->getLabels($items, $delta)),
       '#autocomplete_route_name' => 'entity_reference.autocomplete',
       '#autocomplete_route_parameters' => $autocomplete_route_parameters,
+      '#autocomplete_options' => array(
+        'id_in_text' => TRUE,
+      ),
       '#size' => $this->getSetting('size'),
       '#placeholder' => $this->getSetting('placeholder'),
       '#element_validate' => array(array($this, 'elementValidate')),
diff --git a/core/modules/entity_reference/src/Tests/EntityReferenceAutocompleteTest.php b/core/modules/entity_reference/src/Tests/EntityReferenceAutocompleteTest.php
index ae9ac0c..4509ac4 100644
--- a/core/modules/entity_reference/src/Tests/EntityReferenceAutocompleteTest.php
+++ b/core/modules/entity_reference/src/Tests/EntityReferenceAutocompleteTest.php
@@ -89,12 +89,6 @@ function testEntityReferenceAutocompletion() {
     );
     $this->assertIdentical(reset($data), $target, 'Autocomplete returns only the expected matching entity.');
 
-    // Try to autocomplete a entity label that matches the second entity, and
-    // the first entity  is already typed in the autocomplete (tags) widget.
-    $input = $entity_1->name->value . ' (1), 10/17';
-    $data = $this->getAutocompleteResult('tags', $input);
-    $this->assertIdentical($data[0]['label'], String::checkPlain($entity_2->name->value), 'Autocomplete returned the second matching entity');
-
     // Try to autocomplete a entity label with both a comma and a slash.
     $input = '"label with, and / t';
     $data = $this->getAutocompleteResult('single', $input);
diff --git a/core/modules/system/css/system.module.css b/core/modules/system/css/system.module.css
index cea7a25..07f9cf3 100644
--- a/core/modules/system/css/system.module.css
+++ b/core/modules/system/css/system.module.css
@@ -4,29 +4,6 @@
  */
 
 /**
- * Autocomplete.
- *
- * @see autocomplete.js
- */
-
-/* Animated throbber */
-.js input.form-autocomplete {
-  background-image: url(../../../misc/throbber-inactive.png);
-  background-position: 100% center; /* LTR */
-  background-repeat: no-repeat;
-}
-.js[dir="rtl"] input.form-autocomplete {
-  background-position: 0% center;
-}
-.js input.form-autocomplete.ui-autocomplete-loading {
-  background-image: url(../../../misc/throbber-active.gif);
-  background-position: 100% center; /* LTR */
-}
-.js[dir="rtl"] input.form-autocomplete.ui-autocomplete-loading {
-  background-position: 0% center;
-}
-
-/**
  * Unboxed fieldsets for grouping form elements.
  */
 .fieldgroup {
diff --git a/core/modules/system/css/system.theme.css b/core/modules/system/css/system.theme.css
index 338866b..35fe2a9 100644
--- a/core/modules/system/css/system.theme.css
+++ b/core/modules/system/css/system.theme.css
@@ -195,18 +195,6 @@ label button.link {
   font-weight: bold;
 }
 
-/*
- * Autocomplete.
- *
- * @see autocomplete.js
- */
-/* Suggestion list */
-.ui-autocomplete li.ui-menu-item a.ui-state-focus, .autocomplete li.ui-menu-item a.ui-state-hover {
-  background: #0072b9;
-  color: #fff;
-  margin: 0;
-}
-
 /**
  * Collapsible details.
  *
diff --git a/core/modules/system/src/Form/RegionalForm.php b/core/modules/system/src/Form/RegionalForm.php
index 2d3934f..0550370 100644
--- a/core/modules/system/src/Form/RegionalForm.php
+++ b/core/modules/system/src/Form/RegionalForm.php
@@ -98,6 +98,8 @@ public function buildForm(array $form, FormStateInterface $form_state) {
       '#title' => t('Default time zone'),
       '#default_value' => $system_date->get('timezone.default') ?: date_default_timezone_get(),
       '#options' => $zones,
+      // Use the Select2 library.
+      '#select2' => TRUE,
     );
 
     $configurable_timezones = $system_date->get('timezone.user.configurable');
diff --git a/core/modules/taxonomy/src/Controller/TermAutocompleteController.php b/core/modules/taxonomy/src/Controller/TermAutocompleteController.php
index d701c90..d5d3bec 100644
--- a/core/modules/taxonomy/src/Controller/TermAutocompleteController.php
+++ b/core/modules/taxonomy/src/Controller/TermAutocompleteController.php
@@ -182,13 +182,12 @@ protected function getMatchingTerms($tags_typed, array $vids, $tag_last) {
       ->range(0, 10)
       ->execute();
 
-    $prefix = count($tags_typed) ? Tags::implode($tags_typed) . ', ' : '';
     if (!empty($tids)) {
       $terms = $this->entityManager->getStorage('taxonomy_term')->loadMultiple(array_keys($tids));
       foreach ($terms as $term) {
         // Term names containing commas or quotes must be wrapped in quotes.
         $name = Tags::encode($term->getName());
-        $matches[] = array('value' => $prefix . $name, 'label' => String::checkPlain($term->getName()));
+        $matches[] = array('value' => $name, 'label' => String::checkPlain($term->getName()));
       }
       return $matches;
     }
diff --git a/core/modules/user/src/Tests/UserEntityReferenceTest.php b/core/modules/user/src/Tests/UserEntityReferenceTest.php
index b511203..2ad56eb 100644
--- a/core/modules/user/src/Tests/UserEntityReferenceTest.php
+++ b/core/modules/user/src/Tests/UserEntityReferenceTest.php
@@ -82,7 +82,7 @@ function testUserSelectionByRole() {
     /** @var \Drupal\entity_reference\EntityReferenceAutocomplete $autocomplete */
     $autocomplete = \Drupal::service('entity_reference.autocomplete');
 
-    $matches = $autocomplete->getMatches($field_definition, 'user', 'user', 'NULL', '', 'aabb');
+    $matches = $autocomplete->getMatches($field_definition, 'user', 'user', 'NULL', 'aabb');
     $this->assertEqual(count($matches), 2);
     $users = array();
     foreach ($matches as $match) {
@@ -92,7 +92,7 @@ function testUserSelectionByRole() {
     $this->assertTrue(in_array($user2->label(), $users));
     $this->assertFalse(in_array($user3->label(), $users));
 
-    $matches = $autocomplete->getMatches($field_definition, 'user', 'user', 'NULL', '', 'aabbbb');
+    $matches = $autocomplete->getMatches($field_definition, 'user', 'user', 'NULL', 'aabbbb');
     $this->assertEqual(count($matches), 0, '');
   }
 }
diff --git a/core/themes/seven/css/components/jquery.ui.theme.css b/core/themes/seven/css/components/jquery.ui.theme.css
index b22ca5e..2d95975 100644
--- a/core/themes/seven/css/components/jquery.ui.theme.css
+++ b/core/themes/seven/css/components/jquery.ui.theme.css
@@ -381,10 +381,3 @@
   border-color: #D2D2D2;
   color: #000;
 }
-
-/**
- * Autocomplete
- */
-.ui-autocomplete {
-  background: #fff;
-}
