diff --git a/core/lib/Drupal/Component/Utility/SortArray.php b/core/lib/Drupal/Component/Utility/SortArray.php
index 044a214..4058515 100644
--- a/core/lib/Drupal/Component/Utility/SortArray.php
+++ b/core/lib/Drupal/Component/Utility/SortArray.php
@@ -3,7 +3,16 @@
 namespace Drupal\Component\Utility;
 
 /**
- * Provides generic array sorting helper methods.
+ * Provides comparison callbacks for array sorting with e.g. uasort().
+ *
+ * The methods should really be called compare*() instead of sort*(). The same
+ * can be said about the class name. But since renaming would be an API break,
+ * it is not going to happen.
+ *
+ * Most of the code that currently uses these methods can soon be changed to use
+ * dedicated sort-by-weight methods instead.
+ *
+ * @see \Drupal\Component\Utility\StableSortUtil
  *
  * @ingroup utility
  */
diff --git a/core/lib/Drupal/Component/Utility/StableSortUtil.php b/core/lib/Drupal/Component/Utility/StableSortUtil.php
new file mode 100644
index 0000000..dc7670f
--- /dev/null
+++ b/core/lib/Drupal/Component/Utility/StableSortUtil.php
@@ -0,0 +1,279 @@
+<?php
+
+namespace Drupal\Component\Utility;
+
+/**
+ * Provides "stable" (*) sort functions based on item weights.
+ *
+ * The sort order is based on one or more weights determined from each item.
+ *
+ * (*) How "stable" is the sorting?
+ *
+ * Relative order of items with the same weight is preserved.
+ *
+ * The sort is "stable", if, with the given sort flags, for any two weights
+ * $w0 and $w1 with (string)$w0 !== (string)$w1, either $w0 < $w1 or $w1 < $w0.
+ *
+ * E.g. non-canonical string representations of float weights like '0.0' or
+ * '0.00' in combination with SORT_NUMERIC can lead to non-stable sorting.
+ */
+class StableSortUtil {
+
+  /**
+   * Determines a "neutral weight" based on sort flags, to be used as a fallback
+   * for items without an explicit weight.
+   *
+   * @param int|null $sort_flags
+   *   Either SORT_NUMERIC or SORT_STRING.
+   *
+   * @return int|string
+   *   The "neutral weight".
+   */
+  public static function sortFlagsGetNeutralWeight($sort_flags) {
+    if ($sort_flags === SORT_NUMERIC) {
+      return 0;
+    }
+    else {
+      return '';
+    }
+  }
+
+  /**
+   * Sorts an array of values.
+   *
+   * Keys of the original array are preserved, similar to PHP's native asort().
+   * Relative order of keys with equal values is preserved.
+   *
+   * @param mixed[] $values_unsorted
+   *   Array of values to be sorted. The values should be strings or numbers, or
+   *   anything that can be cleanly converted to string with (string)$value.
+   * @param int $sort_flags
+   *   (optional) One of the PHP sort flags, e.g. SORT_NUMERIC or SORT_STRING.
+   *   Unlike most of the other methods, this one also supports other sort flags
+   *   like SORT_REGULAR etc.
+   * @param int $sort_direction
+   *   (optional) Either SORT_ASC or SORT_DESC.
+   *
+   * @return mixed[]
+   *   Sorted values.
+   */
+  public static function sortByValue(array $values_unsorted, $sort_flags = SORT_NUMERIC, $sort_direction = SORT_ASC) {
+
+    $values_by_weight = [];
+    foreach ($values_unsorted as $k => $value) {
+      // Convert to string to avoid rounding of floats.
+      $values_by_weight[(string)$value][$k] = $value;
+    }
+
+    return self::ksortAndMergeGroups($values_by_weight, $sort_flags, $sort_direction);
+  }
+
+  /**
+   * Sorts an array of arrays by weight key.
+   *
+   * Keys of the original array are preserved, similar to PHP's native asort().
+   * Relative order of items with the same weight is preserved.
+   *
+   * @param array[] $items_unsorted
+   *   Array of arrays to be sorted.
+   *   Each item may or may not have a value (weight) at $item[$weight_key].
+   *   Any such weight must be numeric, or a string.
+   * @param string|int $weight_key
+   *   The key to determine a weight for each item.
+   * @param int $sort_flags
+   *   (optional) Sort flags for PHP's native ksort() function.
+   *   Currently only SORT_NUMERIC or SORT_STRING are supported.
+   * @param int $sort_direction
+   *   (optional) Either SORT_ASC or SORT_DESC.
+   *
+   * @return array[]
+   *   Sorted arrays.
+   */
+  public static function sortByWeightKey(array $items_unsorted, $weight_key, $sort_flags = SORT_NUMERIC, $sort_direction = SORT_ASC) {
+
+    $neutral_weight = self::sortFlagsGetNeutralWeight($sort_flags);
+
+    $items_by_weight = [];
+    foreach ($items_unsorted as $k => $item) {
+      if (isset($item[$weight_key])) {
+        // Convert to string to avoid rounding of floats.
+        $items_by_weight[(string)$item[$weight_key]][$k] = $item;
+      }
+      else {
+        $items_by_weight[$neutral_weight][$k] = $item;
+      }
+    }
+
+    return self::ksortAndMergeGroups($items_by_weight, $sort_flags, $sort_direction);
+  }
+
+  /**
+   * Sorts an array of arrays by multiple weight keys.
+   *
+   * Keys of the original array are preserved, similar to PHP's native asort().
+   * Relative order of items with the same weight is preserved.
+   *
+   * @param array[] $items_unsorted
+   *   Array of arrays to be sorted.
+   *   Each item may or may not have a value (weight) at $item[$weight_key].
+   *   Any such weight must be numeric, or a string.
+   * @param int[] $sort_flags_by_weight_key
+   *   List of sort criteria, highest precedence first.
+   *   Format: $[$weight_key] = $sort_flags
+   *   E.g. ['weight' => SORT_NUMERIC, 'title' => SORT_STRING].
+   *   Currently only SORT_NUMERIC or SORT_STRING are supported.
+   * @param int[] $sort_directions
+   *   (optional) Sort direction for each weight key. If not specified, SORT_ASC
+   *   is assumed.
+   *   E.g. ['title' => SORT_DESC]
+   *
+   * @return array[]
+   *   Sorted arrays.
+   */
+  public static function sortByWeightKeys(array $items_unsorted, array $sort_flags_by_weight_key, array $sort_directions = []) {
+
+    $sort_flags = reset($sort_flags_by_weight_key);
+    $weight_key = key($sort_flags_by_weight_key);
+    unset($sort_flags_by_weight_key[$weight_key]);
+    $neutral_weight = self::sortFlagsGetNeutralWeight($sort_flags);
+    $sort_direction = isset($sort_directions[$weight_key]) ? $sort_directions[$weight_key] : SORT_ASC;
+
+    $items_by_weight = [];
+    foreach ($items_unsorted as $k => $item) {
+      if (isset($item[$weight_key])) {
+        // Convert to string to avoid rounding of floats.
+        $items_by_weight[(string)$item[$weight_key]][$k] = $item;
+      }
+      else {
+        $items_by_weight[$neutral_weight][$k] = $item;
+      }
+    }
+
+    if ($sort_flags_by_weight_key !== []) {
+      // Sort within each group, using the remaining sort criteria.
+      foreach ($items_by_weight as $weight => $items_in_group) {
+        if (count($items_in_group) !== 1) {
+          $items_by_weight[$weight] = self::sortByWeightKeys($items_in_group, $sort_flags_by_weight_key, $sort_directions);
+        }
+      }
+    }
+
+    return self::ksortAndMergeGroups($items_by_weight, $sort_flags, $sort_direction);
+  }
+
+  /**
+   * Sorts an array of objects by weight key.
+   *
+   * Keys of the original array are preserved, similar to PHP's native asort().
+   * Relative order of items with the same weight is preserved.
+   *
+   * @param object[] $objects_unsorted
+   *   Array of objects to be sorted. Each object may or may not have a method
+   *   $object->$weight_method_name().
+   * @param string $weight_method_name
+   *   Method name to determine a weight from each object, e.g. 'getWeight'.
+   * @param int $sort_flags
+   *   (optional) Sort flags for PHP's native ksort() function.
+   *   Currently only SORT_NUMERIC or SORT_STRING are supported.
+   * @param int $sort_direction
+   *   (optional) Either SORT_ASC or SORT_DESC.
+   *
+   * @return object[]
+   */
+  public static function sortByWeightMethod(array $objects_unsorted, $weight_method_name, $sort_flags = SORT_NUMERIC, $sort_direction = SORT_ASC) {
+
+    $neutral_weight = self::sortFlagsGetNeutralWeight($sort_flags);
+
+    $objects_by_weight = [];
+    foreach ($objects_unsorted as $k => $object) {
+      if (method_exists($object, $weight_method_name)) {
+        // Convert to string to avoid rounding of floats.
+        $objects_by_weight[(string)$object->$weight_method_name()][$k] = $object;
+      }
+      else {
+        $objects_by_weight[$neutral_weight][$k] = $object;
+      }
+    }
+
+    return self::ksortAndMergeGroups($objects_by_weight, $sort_flags, $sort_direction);
+  }
+
+  /**
+   * Sorts an array of arbitrary items by a weight determined with a callback.
+   *
+   * Keys of the original array are preserved, similar to PHP's native asort().
+   * Relative order of items with the same weight is preserved.
+   *
+   * @param mixed[] $items_unsorted
+   * @param callable $weight_callback
+   *   Callback to determine a weight for each item.
+   * @param int $sort_flags
+   *   (optional) One of the PHP sort flags, e.g. SORT_NUMERIC or SORT_STRING.
+   *   Unlike most of the other methods, this one also supports other sort flags
+   *   like SORT_REGULAR etc.
+   * @param int $sort_direction
+   *   (optional) Either SORT_ASC or SORT_DESC.
+   *
+   * @return array|mixed
+   */
+  public static function sortByWeightCallback(array $items_unsorted, $weight_callback, $sort_flags = SORT_NUMERIC, $sort_direction = SORT_ASC) {
+
+    $items_by_weight = [];
+    foreach ($items_unsorted as $k => $item) {
+      // Convert to string to avoid rounding of floats.
+      $items_by_weight[(string)call_user_func($weight_callback, $item)][$k] = $item;
+    }
+
+    return self::ksortAndMergeGroups($items_by_weight, $sort_flags, $sort_direction);
+  }
+
+  /**
+   * Sorts and merges items that were previously grouped by weight.
+   *
+   * Keys of the original array are preserved, similar to PHP's native asort().
+   * Relative order of items with the same weight is preserved.
+   *
+   * If a key occurs in more than one group, the current implementation will let
+   * the first occurence win. This behavior is not a guaranteed for future
+   * implementations.
+   *
+   * The method mostly serves as a helper method for the other sort methods
+   * within this class, but it can also be used by custom sort methods outside
+   * this class.
+   *
+   * @param mixed[][] $items_by_weight
+   *   Items grouped by weight.
+   *   Format: $[$weight][$key] = $item
+   * @param int $sort_flags
+   *   (optional) One of the PHP sort flags, e.g. SORT_NUMERIC or SORT_STRING.
+   * @param int $sort_direction
+   *   (optional) Either SORT_ASC or SORT_DESC.
+   *
+   * @return mixed[]
+   *   Items sorted by weight.
+   *   Format: $[$key] = $item
+   */
+  public static function ksortAndMergeGroups(array $items_by_weight, $sort_flags = SORT_NUMERIC, $sort_direction = SORT_ASC) {
+
+    // Sort by weight.
+    if ($sort_direction === SORT_ASC) {
+      ksort($items_by_weight, $sort_flags);
+    }
+    elseif ($sort_direction === SORT_DESC) {
+      krsort($items_by_weight, $sort_flags);
+    }
+    else {
+      $sort_direction_export = var_export($sort_direction, TRUE);
+      throw new \InvalidArgumentException("Sort direction must be either SORT_ASC or SORT_DESC, $sort_direction_export found instead.");
+    }
+
+    // Merge the groups.
+    $items_sorted = [];
+    foreach ($items_by_weight as $weight => $items_in_group) {
+      $items_sorted += $items_in_group;
+    }
+
+    return $items_sorted;
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Component/Utility/StableSortUtilTest.php b/core/tests/Drupal/Tests/Component/Utility/StableSortUtilTest.php
new file mode 100644
index 0000000..237927d
--- /dev/null
+++ b/core/tests/Drupal/Tests/Component/Utility/StableSortUtilTest.php
@@ -0,0 +1,254 @@
+<?php
+
+namespace Drupal\Tests\Component\Utility;
+
+use Drupal\Component\Utility\StableSortUtil;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * Tests the StableSortUtil component.
+ *
+ * @group Utility
+ *
+ * @coversDefaultClass \Drupal\Component\Utility\StableSortUtil
+ */
+class StableSortUtilTest extends UnitTestCase {
+
+  public function testSortFlagsGetNeutralWeight() {
+    static::assertSame(0, StableSortUtil::sortFlagsGetNeutralWeight(SORT_NUMERIC));
+    static::assertSame('', StableSortUtil::sortFlagsGetNeutralWeight(SORT_STRING));
+    static::assertSame('', StableSortUtil::sortFlagsGetNeutralWeight(SORT_STRING | SORT_FLAG_CASE));
+    static::assertSame('', StableSortUtil::sortFlagsGetNeutralWeight(SORT_REGULAR));
+    static::assertSame('', StableSortUtil::sortFlagsGetNeutralWeight(SORT_NATURAL));
+    static::assertSame('', StableSortUtil::sortFlagsGetNeutralWeight(SORT_NATURAL | SORT_FLAG_CASE));
+    static::assertSame('', StableSortUtil::sortFlagsGetNeutralWeight(SORT_LOCALE_STRING));
+  }
+
+  public function testSortByValue() {
+
+    $items_unsorted = [
+      'a' => 0,
+      'b' => 1,
+      'c' => 0,
+      'd' => -1,
+    ];
+
+    $items_sorted_expected = [
+      'd' => -1,
+      'a' => 0,
+      'c' => 0,
+      'b' => 1,
+    ];
+
+    static::assertSameArrays(
+      $items_sorted_expected,
+      StableSortUtil::sortByValue($items_unsorted, SORT_NUMERIC));
+  }
+
+
+  public function testSortByWeightKey() {
+
+    $items_unsorted = [
+      0 => ['pos 0', 'weight' => -10],
+      1 => ['pos 1'],
+      2 => ['pos 2', 'weight' => 2],
+      3 => ['pos 3'],
+      4 => ['pos 4', 'weight' => 0.2],
+      5 => ['pos 5', 'weight' => 0.1],
+      8 => ['pos 8', 'weight' => 0],
+      'x' => ['x', 'weight' => 0],
+      'y' => ['y', 'weight' => 0],
+      'z' => ['z', 'weight' => 2],
+      10 => ['pos 10', 'weight' => -5],
+      11 => ['pos 11', 'weight' => -7],
+    ];
+
+    $items_sorted_expected = [
+      0 => ['pos 0', 'weight' => -10],
+      11 => ['pos 11', 'weight' => -7],
+      10 => ['pos 10', 'weight' => -5],
+      1 => ['pos 1'],
+      3 => ['pos 3'],
+      8 => ['pos 8', 'weight' => 0],
+      'x' => ['x', 'weight' => 0],
+      'y' => ['y', 'weight' => 0],
+      5 => ['pos 5', 'weight' => 0.1],
+      4 => ['pos 4', 'weight' => 0.2],
+      2 => ['pos 2', 'weight' => 2],
+      'z' => ['z', 'weight' => 2],
+    ];
+
+    static::assertSameArrays(
+      $items_sorted_expected,
+      StableSortUtil::sortByWeightKey($items_unsorted, 'weight', SORT_NUMERIC));
+  }
+
+  public static function testSortByWeightKeys() {
+
+    $items_unsorted = [
+      0 => ['pos 0', 'weight' => -10],
+      1 => ['pos 1', 'b'],
+      2 => ['pos 2', 'weight' => 2],
+      3 => ['pos 3', 'a'],
+      4 => ['pos 4', 'weight' => 0.2],
+      5 => ['pos 5', 'weight' => 0.1],
+      8 => ['pos 8', 'weight' => 0],
+      'x' => ['x', 'weight' => 0],
+      'y' => ['y', 'weight' => 0],
+      'z' => ['z', 'weight' => 2],
+      10 => ['pos 10', 'weight' => -5],
+      11 => ['pos 11', 'weight' => -7],
+    ];
+
+    $items_sorted_expected = [
+      0 => ['pos 0', 'weight' => -10],
+      11 => ['pos 11', 'weight' => -7],
+      10 => ['pos 10', 'weight' => -5],
+      8 => ['pos 8', 'weight' => 0],
+      'x' => ['x', 'weight' => 0],
+      'y' => ['y', 'weight' => 0],
+      3 => ['pos 3', 'a'],
+      1 => ['pos 1', 'b'],
+      5 => ['pos 5', 'weight' => 0.1],
+      4 => ['pos 4', 'weight' => 0.2],
+      2 => ['pos 2', 'weight' => 2],
+      'z' => ['z', 'weight' => 2],
+    ];
+
+    $sort_criteria = [
+      'weight' => SORT_NUMERIC,
+      1 => SORT_STRING,
+    ];
+
+    static::assertSameArrays(
+      $items_sorted_expected,
+      StableSortUtil::sortByWeightKeys($items_unsorted, $sort_criteria));
+  }
+
+  public static function testSortByWeightMethod() {
+
+    $items_unsorted = [
+      'a' => new \stdClass,
+      'b' => new StableSortUtilTest_Weighty(0),
+      'c' => new StableSortUtilTest_Weighty(1),
+      'd' => new StableSortUtilTest_Weighty(-1),
+      'e' => new \stdClass,
+      'f' => new StableSortUtilTest_Weighty(-1),
+    ];
+
+    $items_sorted_expected = [
+      'd' => $items_unsorted['d'],
+      'f' => $items_unsorted['f'],
+      'a' => $items_unsorted['a'],
+      'b' => $items_unsorted['b'],
+      'e' => $items_unsorted['e'],
+      'c' => $items_unsorted['c'],
+    ];
+
+    static::assertSameArrays(
+      $items_sorted_expected,
+      StableSortUtil::sortByWeightMethod($items_unsorted, 'getWeight'));
+  }
+
+  public static function testSortByWeightCallback() {
+
+    $items_unsorted = [
+      'a' => 'horse',
+      'b' => 'chicken',
+      'c' => 123,
+      'd' => '',
+      'e' => 'chicken',
+      'f' => '',
+    ];
+
+    $items_sorted_expected = [
+      'd' => '',
+      'f' => '',
+      'c' => 123,
+      'a' => 'horse',
+      'b' => 'chicken',
+      'e' => 'chicken',
+    ];
+
+    static::assertSameArrays(
+      $items_sorted_expected,
+      StableSortUtil::sortByWeightCallback($items_unsorted, 'strlen'));
+  }
+
+  public static function testKsortAndMergeGroups() {
+
+    $items_by_weight = [
+      5 => [
+        'a' => 'A',
+        'b' => 'B',
+      ],
+      0 => [
+        'c' => 'C',
+        'd' => [],
+      ],
+      1 => [
+        'e' => new \stdClass,
+      ],
+      -1 => [
+        'f' => 'F',
+      ],
+    ];
+
+    $items_sorted_expected = [
+      'f' => 'F',
+      'c' => 'C',
+      'd' => [],
+      'e' => $items_by_weight[1]['e'],
+      'a' => 'A',
+      'b' => 'B',
+    ];
+
+    static::assertSameArrays(
+      $items_sorted_expected,
+      StableSortUtil::ksortAndMergeGroups($items_by_weight));
+  }
+
+  /**
+   * @param array[] $expected
+   * @param array[] $actual
+   */
+  private static function assertSameArrays(array $expected, array $actual) {
+    static::assertEquals($expected, $actual);
+    static::assertEquals(self::exportArrayOrder($expected), self::exportArrayOrder($actual));
+    static::assertEquals(array_keys($expected), array_keys($actual));
+    static::assertSame($expected, $actual);
+  }
+
+  /**
+   * @param array[] $items
+   *
+   * @return string
+   */
+  private static function exportArrayOrder(array $items) {
+    return implode(
+      "\n",
+      array_map(
+        'json_encode',
+        $items));
+  }
+
+}
+
+class StableSortUtilTest_Weighty {
+
+  /**
+   * @var float|int|string
+   */
+  private $weight;
+
+  /**
+   * @param string|int|float $weight
+   */
+  public function __construct($weight) {
+    $this->weight = $weight;
+  }
+
+  public function getWeight() {
+    return $this->weight;
+  }
+}
