diff --git a/core/modules/block_place/block_place.install b/core/modules/block_place/block_place.install
new file mode 100644
index 0000000000..c51492ff67
--- /dev/null
+++ b/core/modules/block_place/block_place.install
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * @file
+ * Install, update and uninstall functions for the Block Place module.
+ */
+
+use Drupal\Core\Cache\Cache;
+
+/**
+ * Implements hook_install().
+ */
+function block_place_install() {
+  // This module affects the rendering of blocks and of the page.
+  // @todo Remove in https://www.drupal.org/node/2783791.
+  Cache::invalidateTags(['rendered']);
+
+  // \Drupal\Core\Menu\ContextualLinkManager caches per-group definitions
+  // without associating the cache tag that would allow them to be cleared
+  // by its clearCachedDefinitions() implementation that is automatically
+  // invoked when modules are installed.
+  // @todo Remove when that is fixed in https://www.drupal.org/node/2773591.
+  \Drupal::service('cache.discovery')->deleteAll();
+}
+
diff --git a/core/modules/block_place/block_place.libraries.yml b/core/modules/block_place/block_place.libraries.yml
index 0671252032..87af7de5a3 100644
--- a/core/modules/block_place/block_place.libraries.yml
+++ b/core/modules/block_place/block_place.libraries.yml
@@ -9,3 +9,12 @@ drupal.block_place.icons:
   css:
     theme:
       css/block-place.icons.theme.css: {}
+
+drupal.block_place.js:
+  version: VERSION
+  js:
+    js/block_place.js: {}
+  dependencies:
+    - core/jquery
+    - core/drupal
+
diff --git a/core/modules/block_place/block_place.links.contextual.yml b/core/modules/block_place/block_place.links.contextual.yml
new file mode 100644
index 0000000000..9c482cf154
--- /dev/null
+++ b/core/modules/block_place/block_place.links.contextual.yml
@@ -0,0 +1,4 @@
+block_place.sort:
+  title: 'Sort blocks'
+  route_name: 'block_place.sort'
+  group: 'block'
diff --git a/core/modules/block_place/block_place.module b/core/modules/block_place/block_place.module
index 7fe86a6c7f..e7a9c23c8b 100644
--- a/core/modules/block_place/block_place.module
+++ b/core/modules/block_place/block_place.module
@@ -7,6 +7,7 @@
 
 use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\Core\Url;
+use Drupal\block\Entity\Block;
 
 /**
  * Implements hook_help().
@@ -77,8 +78,81 @@ function block_place_toolbar() {
     '#attached' => [
       'library' => [
         'block_place/drupal.block_place.icons',
+        // This JS library only needs to be attached here so that the new
+        // contextual link works with Ajax. Remove
+        // in https://www.drupal.org/node/2764931.
+        'block_place/drupal.block_place.js',
       ],
-   ],
+    ],
   ];
   return $items;
 }
+
+/**
+ * Implements hook_page_attachments().
+ *
+ * Add block place library if dialog should be open to sort blocks.
+ */
+function block_place_page_attachments(array &$attachments) {
+  $query = \Drupal::request()->query;
+  if ($region_sort = $query->get('block-place-region-sort')) {
+    $attachments['#attached']['library'][] = 'block_place/drupal.block_place.js';
+    $destination = \Drupal::destination()->get();
+    $destination = explode('?', $destination)[0];
+    $theme = \Drupal::theme()->getActiveTheme();
+    $url = Url::fromRoute('block_place.sort', ['region' => $region_sort, 'theme' => $theme->getName()], ['query' => ['destination' => $destination]]);
+    $attachments['#attached']['drupalSettings']['block_place'] = [
+      'dialog_url' => $url->setAbsolute()->toString(),
+      // @todo Always use 'off_canvas' in https://www.drupal.org/node/2784443.
+      'dialog_type' => \Drupal::moduleHandler()->moduleExists('outside_in') ? 'dialog_off_canvas' : 'modal',
+    ];
+  }
+  // Save a setting to flag pages where block_place mode is active.
+  $attachments['#attached']['drupalSettings']['block_place']['isInBlockPlaceMode'] = ($region_sort || $query->get('block-place'));
+}
+
+/**
+ * Implements hook_contextual_links_view_alter().
+ */
+function block_place_contextual_links_view_alter(&$element, $items) {
+  if (isset($element['#links']['block-placesort']) && isset($items['block_configure']['route_parameters']['block'])) {
+
+    // Add arguments to sort blocks url.
+    /** @var \Drupal\block\Entity\Block $block */
+    $block = Block::load($items['block_configure']['route_parameters']['block']);
+    /** @var \Drupal\Core\Url $url */
+    $url = &$element['#links']['block-placesort']['url'];
+    $url->setRouteParameters(
+      [
+        'theme' => $block->getTheme(),
+        'region' => $block->getRegion(),
+      ]
+    );
+
+    // @todo Always use 'off_canvas' in https://www.drupal.org/node/2784443.
+    $attributes = [
+      'class' => ['use-ajax'],
+    ];
+    if (\Drupal::moduleHandler()->moduleExists('outside_in')) {
+      $attributes['data-dialog-type'] = 'dialog';
+      $attributes['data-dialog-renderer'] = 'off_canvas';
+    }
+    else {
+      $attributes['data-dialog-type'] = 'modal';
+    }
+    $element['#links']['block-placesort']['attributes'] = $attributes;
+  }
+}
+
+
+/**
+ * Implements hook_block_view_alter().
+ */
+function block_place_block_view_alter(array &$build) {
+  // Force a new 'data-contextual-id' attribute on blocks when this module is
+  // enabled so as not to reuse stale data cached client-side.
+  // @todo Remove when https://www.drupal.org/node/2773591 is fixed.
+  $build['#contextual_links']['block_place'] = [
+    'route_parameters' => [],
+  ];
+}
diff --git a/core/modules/block_place/block_place.routing.yml b/core/modules/block_place/block_place.routing.yml
new file mode 100644
index 0000000000..402f25980e
--- /dev/null
+++ b/core/modules/block_place/block_place.routing.yml
@@ -0,0 +1,15 @@
+block_place.admin_library:
+  path: 'admin/structure/block-place/library/{theme}'
+  defaults:
+    _controller: '\Drupal\block_place\Controller\PlaceBlockLibraryController::listBlocks'
+    _title: 'Place block'
+  requirements:
+    _access_theme: 'TRUE'
+    _permission: 'administer blocks'
+block_place.sort:
+  path: 'admin/structure/block-place-sort/{theme}/{region}'
+  defaults:
+    _form: '\Drupal\block_place\Form\BlockRegionSorterForm'
+    _title: 'Order Blocks'
+  requirements:
+    _permission: 'administer blocks'
diff --git a/core/modules/block_place/js/block_place.es6.js b/core/modules/block_place/js/block_place.es6.js
new file mode 100644
index 0000000000..e429fea20e
--- /dev/null
+++ b/core/modules/block_place/js/block_place.es6.js
@@ -0,0 +1,37 @@
+/**
+ * @file
+ * Block Place behaviors.
+ */
+
+(function ($, window, Drupal, drupalSettings) {
+  Drupal.blockPlace = {
+    isBlockPlaceMode: () => {
+      if (drupalSettings.hasOwnProperty('block_place') && drupalSettings.block_place.hasOwnProperty('isInBlockPlaceMode')) {
+        return drupalSettings.block_place.isInBlockPlaceMode;
+      }
+      return false;
+    },
+  };
+
+  Drupal.behaviors.blockPlace = {
+    attach(context, settings) {
+      // If drupalSettings.block_place is set open open dialog.
+      if (drupalSettings.hasOwnProperty('block_place') && drupalSettings.block_place.hasOwnProperty('dialog_url')) {
+        $(window).once('block_sort').each(() => {
+          Drupal.ajax({
+            dialog: {},
+            dialogType: drupalSettings.block_place.dialog_type,
+            url: drupalSettings.block_place.dialog_url,
+            progress: { type: 'throbber' },
+          }).execute();
+        });
+      }
+    },
+  };
+
+  // Make sure contextual links work with Ajax.
+  // Remove in https://www.drupal.org/node/2764931.
+  $(document).once('contextual-ajax').on('drupalContextualLinkAdded', (event, data) => {
+    Drupal.attachBehaviors(data.$el[0]);
+  });
+}(jQuery, window, Drupal, drupalSettings));
diff --git a/core/modules/block_place/js/block_place.js b/core/modules/block_place/js/block_place.js
new file mode 100644
index 0000000000..fcbb8370aa
--- /dev/null
+++ b/core/modules/block_place/js/block_place.js
@@ -0,0 +1,36 @@
+/**
+* DO NOT EDIT THIS FILE.
+* See the following change record for more information,
+* https://www.drupal.org/node/2815083
+* @preserve
+**/
+
+(function ($, window, Drupal, drupalSettings) {
+  Drupal.blockPlace = {
+    isBlockPlaceMode: function isBlockPlaceMode() {
+      if (drupalSettings.hasOwnProperty('block_place') && drupalSettings.block_place.hasOwnProperty('isInBlockPlaceMode')) {
+        return drupalSettings.block_place.isInBlockPlaceMode;
+      }
+      return false;
+    }
+  };
+
+  Drupal.behaviors.blockPlace = {
+    attach: function attach(context, settings) {
+      if (drupalSettings.hasOwnProperty('block_place') && drupalSettings.block_place.hasOwnProperty('dialog_url')) {
+        $(window).once('block_sort').each(function () {
+          Drupal.ajax({
+            dialog: {},
+            dialogType: drupalSettings.block_place.dialog_type,
+            url: drupalSettings.block_place.dialog_url,
+            progress: { type: 'throbber' }
+          }).execute();
+        });
+      }
+    }
+  };
+
+  $(document).once('contextual-ajax').on('drupalContextualLinkAdded', function (event, data) {
+    Drupal.attachBehaviors(data.$el[0]);
+  });
+})(jQuery, window, Drupal, drupalSettings);
\ No newline at end of file
diff --git a/core/modules/block_place/src/Controller/PlaceBlockLibraryController.php b/core/modules/block_place/src/Controller/PlaceBlockLibraryController.php
new file mode 100644
index 0000000000..b44f365b5a
--- /dev/null
+++ b/core/modules/block_place/src/Controller/PlaceBlockLibraryController.php
@@ -0,0 +1,46 @@
+<?php
+
+namespace Drupal\block_place\Controller;
+
+use Drupal\block\Controller\BlockLibraryController;
+use Drupal\Component\Serialization\Json;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Block library controller that extends block module's to use off_canvas tray.
+ *
+ * @package Drupal\block_place\Controller
+ */
+class PlaceBlockLibraryController extends BlockLibraryController {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function listBlocks(Request $request, $theme) {
+    $build = parent::listBlocks($request, $theme);
+    // Alter all 'Place Block' links to use the Offcanvas tray.
+    if (isset($build['blocks']['#rows'])) {
+      // @todo Always use 'off_canvas' in https://www.drupal.org/node/2784443.
+      if ($this->moduleHandler()->moduleExists('outside_in')) {
+        $data_dialog_attributes = [
+          'data-dialog-type' => 'dialog',
+          'data-dialog-renderer' => 'off_canvas',
+          'data-dialog-options' => Json::encode([
+            'width' => 425,
+          ]),
+        ];
+      }
+      else {
+        $data_dialog_attributes = [];
+      }
+      foreach ($build['blocks']['#rows'] as &$row) {
+        if (isset($row['operations']['data']['#links']['add'])) {
+          $row['operations']['data']['#links']['add']['attributes'] = $data_dialog_attributes + $row['operations']['data']['#links']['add']['attributes'];
+          $row['operations']['data']['#links']['add']['query']['destination'] = \Drupal::destination()->get();
+        }
+      }
+    }
+    return $build;
+  }
+
+}
diff --git a/core/modules/block_place/src/Form/BlockRegionSorterForm.php b/core/modules/block_place/src/Form/BlockRegionSorterForm.php
new file mode 100644
index 0000000000..b47dd9aaac
--- /dev/null
+++ b/core/modules/block_place/src/Form/BlockRegionSorterForm.php
@@ -0,0 +1,114 @@
+<?php
+
+namespace Drupal\block_place\Form;
+
+use Drupal\block\Entity\Block;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Form\FormBase;
+use Drupal\Core\Form\FormStateInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Provides a block for sorting block within a region.
+ */
+class BlockRegionSorterForm extends FormBase {
+
+  /**
+   * The entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * Constructs a new BlockRegionSorterForm.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The entity type manager.
+   */
+  public function __construct(EntityTypeManagerInterface $entity_type_manager) {
+    $this->entityTypeManager = $entity_type_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('entity_type.manager')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state, $theme = NULL, $region = NULL) {
+    $blocks = $this->entityTypeManager->getStorage('block')->loadByProperties(['region' => $region, 'theme' => $theme]);
+    // Make sure the blocks are in the correct order.
+    usort($blocks, function (Block $blocka, Block $blockb) {
+      return $blocka->getWeight() - $blockb->getWeight();
+    });
+    $form['#tree'] = TRUE;
+    $form['blocks'] = [
+      '#type' => 'table',
+      '#header' => [$this->t('Block'), $this->t('Weight')],
+      '#empty' => $this->t('There are no blocks in this regions'),
+      '#tabledrag' => [
+        [
+          'action' => 'order',
+          'relationship' => 'sibling',
+          'group' => 'block-table-order-weight',
+        ],
+      ],
+      '#attributes' => [
+        'id' => 'block-place-sort-table',
+      ],
+    ];
+
+    /** @var \Drupal\block\Entity\Block $block */
+    foreach ($blocks as $block) {
+      $form['blocks'][$block->id()]['#weight'] = $block->getWeight();
+      $form['blocks'][$block->id()]['#attributes']['class'][] = 'draggable';
+      $form['blocks'][$block->id()]['label'] = [
+        '#plain_text' => $block->label(),
+      ];
+
+      $form['blocks'][$block->id()]['weight'] = [
+        '#type' => 'weight',
+        '#title' => $this->t('Weight for @title', ['@title' => $block->label()]),
+        '#title_display' => 'invisible',
+        '#default_value' => $block->getWeight(),
+        // Classify the weight element for #tabledrag.
+        '#attributes' => ['class' => ['block-table-order-weight']],
+      ];
+    }
+
+    $form['actions'] = ['#type' => 'actions'];
+    $form['actions']['submit'] = [
+      '#type' => 'submit',
+      '#value' => $this->t('Save changes'),
+    ];
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'block_place_sort';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    foreach ($form_state->getValue('blocks') as $block_id => $block_weight) {
+      /** @var \Drupal\block\Entity\Block $block */
+      $block = $this->entityTypeManager->getStorage('block')->load($block_id);
+      $block->setWeight($block_weight['weight']);
+      $block->save();
+    }
+    drupal_set_message('The block order has been saved');
+  }
+
+}
diff --git a/core/modules/block_place/src/Plugin/DisplayVariant/PlaceBlockPageVariant.php b/core/modules/block_place/src/Plugin/DisplayVariant/PlaceBlockPageVariant.php
index d05c7f172f..2a130a6249 100644
--- a/core/modules/block_place/src/Plugin/DisplayVariant/PlaceBlockPageVariant.php
+++ b/core/modules/block_place/src/Plugin/DisplayVariant/PlaceBlockPageVariant.php
@@ -5,10 +5,12 @@
 use Drupal\block\BlockRepositoryInterface;
 use Drupal\block\Plugin\DisplayVariant\BlockPageVariant;
 use Drupal\Component\Serialization\Json;
+use Drupal\Core\Entity\EntityStorageInterface;
 use Drupal\Core\Entity\EntityViewBuilderInterface;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Link;
 use Drupal\Core\Routing\RedirectDestinationInterface;
 use Drupal\Core\Theme\ThemeManagerInterface;
-use Drupal\Core\Link;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -36,6 +38,20 @@ class PlaceBlockPageVariant extends BlockPageVariant {
   protected $redirectDestination;
 
   /**
+   * The module handler.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface
+   */
+  protected $moduleHandler;
+
+  /**
+   * The block storage handler.
+   *
+   * @var \Drupal\Core\Entity\EntityStorageInterface
+   */
+  protected $blockStorage;
+
+  /**
    * Constructs a new PlaceBlockPageVariant.
    *
    * @param array $configuration
@@ -54,12 +70,18 @@ class PlaceBlockPageVariant extends BlockPageVariant {
    *   The theme manager.
    * @param \Drupal\Core\Routing\RedirectDestinationInterface $redirect_destination
    *   The redirect destination.
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler.
+   * @param \Drupal\Core\Entity\EntityStorageInterface $block_storage
+   *   The block storage handler.
    */
-  public function __construct(array $configuration, $plugin_id, $plugin_definition, BlockRepositoryInterface $block_repository, EntityViewBuilderInterface $block_view_builder, array $block_list_cache_tags, ThemeManagerInterface $theme_manager, RedirectDestinationInterface $redirect_destination) {
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, BlockRepositoryInterface $block_repository, EntityViewBuilderInterface $block_view_builder, array $block_list_cache_tags, ThemeManagerInterface $theme_manager, RedirectDestinationInterface $redirect_destination, ModuleHandlerInterface $module_handler, EntityStorageInterface $block_storage) {
     parent::__construct($configuration, $plugin_id, $plugin_definition, $block_repository, $block_view_builder, $block_list_cache_tags);
 
     $this->themeManager = $theme_manager;
     $this->redirectDestination = $redirect_destination;
+    $this->moduleHandler = $module_handler;
+    $this->blockStorage = $block_storage;
   }
 
   /**
@@ -74,7 +96,9 @@ public static function create(ContainerInterface $container, array $configuratio
       $container->get('entity_type.manager')->getViewBuilder('block'),
       $container->get('entity_type.manager')->getDefinition('block')->getListCacheTags(),
       $container->get('theme.manager'),
-      $container->get('redirect.destination')
+      $container->get('redirect.destination'),
+      $container->get('module_handler'),
+      $container->get('entity_type.manager')->getStorage('block')
     );
   }
 
@@ -96,24 +120,50 @@ public function build() {
       $query = [
         'region' => $region,
       ];
+
+
       if ($destination) {
         $query['destination'] = $destination;
+        if ($this->blockStorage->loadByProperties(['theme' => $theme_name, 'region' => $region])) {
+          $query['destination'] .= "?block-place-region-sort=$region";
+        }
+
       }
       $title = $this->t('<span class="visually-hidden">Place block in the %region region</span>', ['%region' => $region_name]);
+      // @todo Remove module exists check when off_canvas library moved into
+      //   core.services.yml. https://www.drupal.org/node/2784443
+      if ($this->moduleHandler->moduleExists('outside_in')) {
+        $place_block_route = 'block_place.admin_library';
+        $data_dialog_attributes = [
+          'data-dialog-type' => 'dialog',
+          'data-dialog-renderer' => 'off_canvas',
+          'data-dialog-options' => Json::encode([
+            'width' => 350,
+          ]),
+        ];
+      }
+      else {
+        $place_block_route = 'block.admin_library';
+        $data_dialog_attributes = [
+          'data-dialog-type' => 'modal',
+          'data-dialog-options' => Json::encode([
+            'width' => 700,
+          ]),
+        ];
+      }
+
+      $place_block_route = 'block_place.admin_library';
+
       $operations['block_description'] = [
         '#type' => 'inline_template',
         '#template' => '<div class="block-place-region">{{ link }}</div>',
         '#context' => [
-          'link' => Link::createFromRoute($title, 'block.admin_library', ['theme' => $theme_name], [
+          'link' => Link::createFromRoute($title, $place_block_route, ['theme' => $theme_name], [
             'query' => $query,
             'attributes' => [
               'title' => $title,
               'class' => ['use-ajax', 'button', 'button--small'],
-              'data-dialog-type' => 'modal',
-              'data-dialog-options' => Json::encode([
-                'width' => 700,
-              ]),
-            ],
+            ] + $data_dialog_attributes,
           ]),
         ],
       ];
diff --git a/core/modules/block_place/tests/src/Functional/BlockPlaceTest.php b/core/modules/block_place/tests/src/Functional/BlockPlaceTest.php
index 8e89048acd..d7e965a8d8 100644
--- a/core/modules/block_place/tests/src/Functional/BlockPlaceTest.php
+++ b/core/modules/block_place/tests/src/Functional/BlockPlaceTest.php
@@ -39,7 +39,7 @@ public function testPlacingBlocksAdmin() {
     $this->assertGreaterThan(0, count($visible_regions));
 
     $default_theme = $this->config('system.theme')->get('default');
-    $block_library_url = Url::fromRoute('block.admin_library', ['theme' => $default_theme]);
+    $block_library_url = Url::fromRoute('block_place.admin_library', ['theme' => $default_theme]);
     foreach ($visible_regions as $region => $name) {
       $block_library_url->setOption('query', ['region' => $region]);
       $links = $this->xpath('//a[contains(@href, :href)]', [':href' => $block_library_url->toString()]);
diff --git a/core/modules/outside_in/js/outside_in.es6.js b/core/modules/outside_in/js/outside_in.es6.js
index 66708cea0d..883ee66a33 100644
--- a/core/modules/outside_in/js/outside_in.es6.js
+++ b/core/modules/outside_in/js/outside_in.es6.js
@@ -193,7 +193,10 @@
     attach() {
       const editMode = localStorage.getItem('Drupal.contextualToolbar.isViewing') === 'false';
       if (editMode) {
-        setEditModeState(true);
+        // Do turn on edit mode when on block
+        if (!Drupal.hasOwnProperty('blockPlace') || !Drupal.blockPlace.isBlockPlaceMode()) {
+          setEditModeState(true);
+        }
       }
     },
   };
diff --git a/core/modules/outside_in/js/outside_in.js b/core/modules/outside_in/js/outside_in.js
index 635196d88f..1019eea683 100644
--- a/core/modules/outside_in/js/outside_in.js
+++ b/core/modules/outside_in/js/outside_in.js
@@ -116,7 +116,9 @@
     attach: function attach() {
       var editMode = localStorage.getItem('Drupal.contextualToolbar.isViewing') === 'false';
       if (editMode) {
-        setEditModeState(true);
+        if (!Drupal.hasOwnProperty('blockPlace') || !Drupal.blockPlace.isBlockPlaceMode()) {
+          setEditModeState(true);
+        }
       }
     }
   };
