diff --git a/core/modules/contextual/src/Element/ContextualLinks.php b/core/modules/contextual/src/Element/ContextualLinks.php
index 915a3132ca687dbbdc6cd432fa6d0d84555ade3d..9ac0f42d1048e9738ecae1103b9732031bc485b3 100644
--- a/core/modules/contextual/src/Element/ContextualLinks.php
+++ b/core/modules/contextual/src/Element/ContextualLinks.php
@@ -3,6 +3,7 @@
 namespace Drupal\contextual\Element;
 
 use Drupal\Component\Utility\Html;
+use Drupal\Component\Utility\SortArray;
 use Drupal\Core\Render\Element\RenderElement;
 use Drupal\Core\Url;
 
@@ -72,6 +73,8 @@ public static function preRenderLinks(array $element) {
       $items += $contextual_links_manager->getContextualLinksArrayByGroup($group, $args['route_parameters'], $args['metadata']);
     }
 
+    uasort($items, [SortArray::class, 'sortByWeightElement']);
+
     // Transform contextual links into parameters suitable for links.html.twig.
     $links = [];
     foreach ($items as $class => $item) {
diff --git a/core/modules/contextual/tests/src/Functional/ContextualDynamicContextTest.php b/core/modules/contextual/tests/src/Functional/ContextualDynamicContextTest.php
index f1fb389b0d8e34fd6be488c65ecc5fabcbb41b68..b2aba0902ea85856a39ce2644515f61cae17ec31 100644
--- a/core/modules/contextual/tests/src/Functional/ContextualDynamicContextTest.php
+++ b/core/modules/contextual/tests/src/Functional/ContextualDynamicContextTest.php
@@ -161,6 +161,11 @@ public function testDifferentPermissions() {
     $this->drupalGet(Url::fromRoute('menu_test.contextual_test'));
     $this->assertSession()->assertEscaped("<script>alert('Welcome to the jungle!')</script>");
     $this->assertSession()->responseContains('<li><a href="' . base_path() . 'menu-test-contextual/1/edit" class="use-ajax" data-dialog-type="modal" data-is-something>Edit menu - contextual</a></li>');
+    // Test contextual links respects the weight set in *.links.contextual.yml.
+    $firstLink = $this->assertSession()->elementExists('css', 'ul.contextual-links li:nth-of-type(1) a');
+    $secondLink = $this->assertSession()->elementExists('css', 'ul.contextual-links li:nth-of-type(2) a');
+    $this->assertEquals(base_path() . 'menu-test-contextual/1/edit', $firstLink->getAttribute('href'));
+    $this->assertEquals(base_path() . 'menu-test-contextual/1', $secondLink->getAttribute('href'));
   }
 
   /**
diff --git a/core/modules/system/tests/modules/menu_test/menu_test.links.contextual.yml b/core/modules/system/tests/modules/menu_test/menu_test.links.contextual.yml
index 2849592433d67652df2c4f8c3f33e24b7079a696..2b633b910bc9e32393b52a82b3e9cee3c9e530d6 100644
--- a/core/modules/system/tests/modules/menu_test/menu_test.links.contextual.yml
+++ b/core/modules/system/tests/modules/menu_test/menu_test.links.contextual.yml
@@ -3,6 +3,7 @@ menu_test.contextual_hidden_manage:
   group: menu_test_menu
   route_name: menu_test.contextual_hidden_manage
   class: '\Drupal\menu_test\Plugin\Menu\ContextualLink\TestContextualLink'
+  weight: 2
 
 menu_test.contextual_hidden_manage_edit:
   title: 'Edit menu - contextual'
@@ -13,8 +14,10 @@ menu_test.contextual_hidden_manage_edit:
       class: ['use-ajax']
       data-dialog-type: 'modal'
       data-is-something: TRUE
+  weight: 1
 
 menu_test.hidden_block_configure:
   title: 'Configure block'
   group: menu_test_block
   route_name: menu_test.hidden_block_configure
+  weight: 0