diff --git a/core/composer.json b/core/composer.json
index bda527f..37b81fa 100644
--- a/core/composer.json
+++ b/core/composer.json
@@ -111,6 +111,7 @@
         "drupal/image": "self.version",
         "drupal/inline_form_errors": "self.version",
         "drupal/language": "self.version",
+        "drupal/layout_builder": "self.version",
         "drupal/layout_discovery": "self.version",
         "drupal/link": "self.version",
         "drupal/locale": "self.version",
diff --git a/core/lib/Drupal/Core/Layout/IconGenerator.php b/core/lib/Drupal/Core/Layout/IconGenerator.php
new file mode 100644
index 0000000..1daa0b0
--- /dev/null
+++ b/core/lib/Drupal/Core/Layout/IconGenerator.php
@@ -0,0 +1,88 @@
+<?php
+
+namespace Drupal\Core\Layout;
+
+/**
+ * Generates layout icons from well-formed config.
+ */
+class IconGenerator implements IconGeneratorInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function generateSvgFromIconMap(array $icon_map, $width = 250, $height = 300, $stroke_width = 2, $padding = 5, $fill = 'lightgray', $stroke = 'black') {
+    $build = [
+      '#type' => 'html_tag',
+      '#tag' => 'svg',
+      '#attributes' => [
+        'width' => $width,
+        'height' => $height,
+      ],
+    ];
+
+    $region_rects = [];
+    $num_rows = count($icon_map);
+    foreach ($icon_map as $row => $cols) {
+      $num_cols = count($cols);
+      foreach ($cols as $col => $region) {
+        if (!isset($region_rects[$region])) {
+          // The first instance of a region is always the starting point.
+          $x = $col * ($width / $num_cols);
+          $y = ($row / $num_rows) * $height;
+          $region_rects[$region] = [
+            'x' => $x,
+            'y' => $y,
+            'width' => ($width / $num_cols) - $padding,
+            'height' => ($height / $num_rows) - $padding,
+            'last_col' => $col,
+            'last_row' => $row,
+          ];
+        }
+        else {
+          // Only increase the width/height if we've moved in that direction.
+          if ($region_rects[$region]['last_col'] != $col) {
+            $region_rects[$region]['width'] += ($width / $num_cols);
+            $region_rects[$region]['last_col'] = $col;
+          }
+          if ($region_rects[$region]['last_row'] != $row) {
+            $region_rects[$region]['height'] += ($height / $num_rows);
+            $region_rects[$region]['last_row'] = $row;
+          }
+        }
+      }
+    }
+
+    // Append each polygon to the SVG.
+    foreach ($region_rects as $region => $attributes) {
+      // Group our regions allows for metadata, nested elements, and tooltips.
+      $build[$region] = [
+        '#type' => 'html_tag',
+        '#tag' => 'g',
+      ];
+
+      $build[$region]['title'] = [
+        '#type' => 'html_tag',
+        '#tag' => 'title',
+        '#value' => $region,
+      ];
+
+      // Assemble the rectangle SVG element.
+      $build[$region]['rect'] = [
+        '#type' => 'html_tag',
+        '#tag' => 'rect',
+        '#attributes' => [
+          'x' => $attributes['x'],
+          'y' => $attributes['y'],
+          'width' => $attributes['width'],
+          'height' => $attributes['height'],
+          'fill' => $fill,
+          'stroke' => $stroke,
+          'stroke-width' => $stroke_width,
+        ],
+      ];
+    }
+
+    return $build;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Layout/IconGeneratorInterface.php b/core/lib/Drupal/Core/Layout/IconGeneratorInterface.php
new file mode 100644
index 0000000..711f8c9
--- /dev/null
+++ b/core/lib/Drupal/Core/Layout/IconGeneratorInterface.php
@@ -0,0 +1,52 @@
+<?php
+
+namespace Drupal\Core\Layout;
+
+/**
+ * Provides an interface for generating layout icons from well-formed config.
+ */
+interface IconGeneratorInterface {
+
+  /**
+   * Generates a SVG based on a Layout's icon map.
+   *
+   * @param array $icon_map
+   *   A two dimensional array representing the visual output of the layout.
+   *   For the following shape:
+   *   |------------------------------|
+   *   |                              |
+   *   |             100%             |
+   *   |                              |
+   *   |-------|--------------|-------|
+   *   |       |              |       |
+   *   |  25%  |      50%     |  25%  |
+   *   |       |              |       |
+   *   |-------|--------------|-------|
+   *   |                              |
+   *   |             100%             |
+   *   |                              |
+   *   |------------------------------|
+   *   The corresponding array would be:
+   *   - [top]
+   *   - [first, second, second, third]
+   *   - [bottom].
+   * @param int $width
+   *   (optional) The width of the generated SVG. Defaults to 250.
+   * @param int $height
+   *   (optional) The height of the generated SVG. Defaults to 300.
+   * @param int $stroke_width
+   *   (optional) The width of region borders. Defaults to 2.
+   * @param int $padding
+   *   (optional) The padding between regions. Any value above 0 is valid.
+   *   Defaults to 5.
+   * @param string $fill
+   *   (optional) The fill color of regions. Defaults to 'lightgray'.
+   * @param string $stroke
+   *   (optional) The color of region borders. Defaults to 'black'.
+   *
+   * @return array
+   *   A render array representing a SVG icon.
+   */
+  public function generateSvgFromIconMap(array $icon_map, $width = 250, $height = 300, $stroke_width = 2, $padding = 5, $fill = 'lightgray', $stroke = 'black');
+
+}
diff --git a/core/lib/Drupal/Core/Layout/LayoutDefinition.php b/core/lib/Drupal/Core/Layout/LayoutDefinition.php
index c804776..a2be60b 100644
--- a/core/lib/Drupal/Core/Layout/LayoutDefinition.php
+++ b/core/lib/Drupal/Core/Layout/LayoutDefinition.php
@@ -372,6 +372,52 @@ public function setIconPath($icon) {
   }
 
   /**
+   * Builds a render array for an icon representing the layout.
+   *
+   * @param int $width
+   *   (optional) The width of the icon. Defaults to 250.
+   * @param int $height
+   *   (optional) The height of the icon. Defaults to 300.
+   * @param int $stroke_width
+   *   (optional) If a generated SVG is used, the width of region borders.
+   *   Defaults to 2.
+   * @param int $padding
+   *   (optional) If a generated SVG is used, the padding between regions. Any
+   *   value above 0 is valid. Defaults to 5.
+   * @param string $fill
+   *   (optional) If a generated SVG is used, the fill color of regions.
+   *   Defaults to 'lightgray'.
+   * @param string $stroke
+   *   (optional) If a generated SVG is used, the color of region borders.
+   *   Defaults to 'black'.
+   *
+   * @return array
+   *   A render array for the icon.
+   */
+  public function getIcon($width = 250, $height = 300, $stroke_width = 2, $padding = 5, $fill = 'lightgray', $stroke = 'black') {
+    $icon = [];
+    if ($icon_path = $this->getIconPath()) {
+      $icon = [
+        '#theme' => 'image',
+        '#uri' => $icon_path,
+        '#width' => $width,
+        '#height' => $height,
+      ];
+    }
+    elseif ($icon_map = $this->get('icon_map')) {
+      $icon = $this->getIconGenerator()->generateSvgFromIconMap($icon_map, $width, $height, $stroke_width, $padding, $fill, $stroke);
+    }
+    return $icon;
+  }
+
+  /**
+   * @return \Drupal\Core\Layout\IconGenerator
+   */
+  protected function getIconGenerator() {
+    return \Drupal::service('layout.icon_generator');
+  }
+
+  /**
    * Gets the regions for this layout definition.
    *
    * @return array[]
diff --git a/core/misc/ajax.es6.js b/core/misc/ajax.es6.js
index 6248e46..27795c8 100644
--- a/core/misc/ajax.es6.js
+++ b/core/misc/ajax.es6.js
@@ -47,25 +47,7 @@ function loadAjaxBehavior(base) {
       }
 
       // Bind Ajax behaviors to all items showing the class.
-      $('.use-ajax').once('ajax').each(function () {
-        const element_settings = {};
-        // Clicked links look better with the throbber than the progress bar.
-        element_settings.progress = { type: 'throbber' };
-
-        // For anchor tags, these will go to the target of the anchor rather
-        // than the usual location.
-        const href = $(this).attr('href');
-        if (href) {
-          element_settings.url = href;
-          element_settings.event = 'click';
-        }
-        element_settings.dialogType = $(this).data('dialog-type');
-        element_settings.dialogRenderer = $(this).data('dialog-renderer');
-        element_settings.dialog = $(this).data('dialog-options');
-        element_settings.base = $(this).attr('id');
-        element_settings.element = this;
-        Drupal.ajax(element_settings);
-      });
+      Drupal.ajax.bindAjaxLinks($('body'));
 
       // This class means to submit the form to the action using Ajax.
       $('.use-ajax-submit').once('ajax').each(function () {
@@ -269,6 +251,27 @@ function loadAjaxBehavior(base) {
   Drupal.ajax.expired = function () {
     return Drupal.ajax.instances.filter(instance => instance && instance.element !== false && !document.body.contains(instance.element));
   };
+  Drupal.ajax.bindAjaxLinks = ($element) => {
+    $element.find('.use-ajax').once('ajax').each(function () {
+      const element_settings = {};
+      // Clicked links look better with the throbber than the progress bar.
+      element_settings.progress = { type: 'throbber' };
+
+      // For anchor tags, these will go to the target of the anchor rather
+      // than the usual location.
+      const href = $(this).attr('href');
+      if (href) {
+        element_settings.url = href;
+        element_settings.event = 'click';
+      }
+      element_settings.dialogType = $(this).data('dialog-type');
+      element_settings.dialogRenderer = $(this).data('dialog-renderer');
+      element_settings.dialog = $(this).data('dialog-options');
+      element_settings.base = $(this).attr('id');
+      element_settings.element = this;
+      Drupal.ajax(element_settings);
+    });
+  };
 
   /**
    * Settings for an Ajax object.
@@ -518,13 +521,15 @@ else if (this.element && element.form) {
     else {
       ajax.options.url += '&';
     }
-    // If this element has a dialog type use if for the wrapper if not use 'ajax'.
-    let wrapper = `drupal_${(element_settings.dialogType || 'ajax')}`;
-    if (element_settings.dialogRenderer) {
-      wrapper += `.${element_settings.dialogRenderer}`;
+    // Add a wrapper format, if none exists.
+    if (ajax.options.url.indexOf(`${Drupal.ajax.WRAPPER_FORMAT}`) === -1) {
+      // If this element has a dialog type use if for the wrapper if not use 'ajax'.
+      let wrapper = `drupal_${(element_settings.dialogType || 'ajax')}`;
+      if (element_settings.dialogRenderer) {
+        wrapper += `.${element_settings.dialogRenderer}`;
+      }
+      ajax.options.url += `${Drupal.ajax.WRAPPER_FORMAT}=${wrapper}`;
     }
-    ajax.options.url += `${Drupal.ajax.WRAPPER_FORMAT}=${wrapper}`;
-
 
     // Bind the ajaxSubmit function to the element event.
     $(ajax.element).on(element_settings.event, function (event) {
@@ -1338,4 +1343,7 @@ else if (effect.showEffect !== 'show') {
       }
     },
   };
+  $(document).on('drupalContextualLinkAdded', (event, data) => {
+    Drupal.ajax.bindAjaxLinks($(data.$el[0]));
+  });
 }(jQuery, window, Drupal, drupalSettings));
diff --git a/core/misc/ajax.js b/core/misc/ajax.js
index 5ea5242..07644bd 100644
--- a/core/misc/ajax.js
+++ b/core/misc/ajax.js
@@ -27,23 +27,7 @@ function loadAjaxBehavior(base) {
         }
       }
 
-      $('.use-ajax').once('ajax').each(function () {
-        var element_settings = {};
-
-        element_settings.progress = { type: 'throbber' };
-
-        var href = $(this).attr('href');
-        if (href) {
-          element_settings.url = href;
-          element_settings.event = 'click';
-        }
-        element_settings.dialogType = $(this).data('dialog-type');
-        element_settings.dialogRenderer = $(this).data('dialog-renderer');
-        element_settings.dialog = $(this).data('dialog-options');
-        element_settings.base = $(this).attr('id');
-        element_settings.element = this;
-        Drupal.ajax(element_settings);
-      });
+      Drupal.ajax.bindAjaxLinks($('body'));
 
       $('.use-ajax-submit').once('ajax').each(function () {
         var element_settings = {};
@@ -138,6 +122,25 @@ function loadAjaxBehavior(base) {
       return instance && instance.element !== false && !document.body.contains(instance.element);
     });
   };
+  Drupal.ajax.bindAjaxLinks = function ($element) {
+    $element.find('.use-ajax').once('ajax').each(function () {
+      var element_settings = {};
+
+      element_settings.progress = { type: 'throbber' };
+
+      var href = $(this).attr('href');
+      if (href) {
+        element_settings.url = href;
+        element_settings.event = 'click';
+      }
+      element_settings.dialogType = $(this).data('dialog-type');
+      element_settings.dialogRenderer = $(this).data('dialog-renderer');
+      element_settings.dialog = $(this).data('dialog-options');
+      element_settings.base = $(this).attr('id');
+      element_settings.element = this;
+      Drupal.ajax(element_settings);
+    });
+  };
 
   Drupal.Ajax = function (base, element, element_settings) {
     var defaults = {
@@ -242,11 +245,13 @@ function loadAjaxBehavior(base) {
       ajax.options.url += '&';
     }
 
-    var wrapper = 'drupal_' + (element_settings.dialogType || 'ajax');
-    if (element_settings.dialogRenderer) {
-      wrapper += '.' + element_settings.dialogRenderer;
+    if (ajax.options.url.indexOf('' + Drupal.ajax.WRAPPER_FORMAT) === -1) {
+      var wrapper = 'drupal_' + (element_settings.dialogType || 'ajax');
+      if (element_settings.dialogRenderer) {
+        wrapper += '.' + element_settings.dialogRenderer;
+      }
+      ajax.options.url += Drupal.ajax.WRAPPER_FORMAT + '=' + wrapper;
     }
-    ajax.options.url += Drupal.ajax.WRAPPER_FORMAT + '=' + wrapper;
 
     $(ajax.element).on(element_settings.event, function (event) {
       if (!drupalSettings.ajaxTrustedUrl[ajax.url] && !Drupal.url.isLocal(ajax.url)) {
@@ -588,4 +593,7 @@ function loadAjaxBehavior(base) {
       }
     }
   };
+  $(document).on('drupalContextualLinkAdded', function (event, data) {
+    Drupal.ajax.bindAjaxLinks($(data.$el[0]));
+  });
 })(jQuery, window, Drupal, drupalSettings);
\ No newline at end of file
diff --git a/core/modules/field_layout/src/Form/FieldLayoutEntityDisplayFormTrait.php b/core/modules/field_layout/src/Form/FieldLayoutEntityDisplayFormTrait.php
index 043e5c7..170bcd0 100644
--- a/core/modules/field_layout/src/Form/FieldLayoutEntityDisplayFormTrait.php
+++ b/core/modules/field_layout/src/Form/FieldLayoutEntityDisplayFormTrait.php
@@ -87,6 +87,8 @@ public function form(array $form, FormStateInterface $form_state) {
       '#tree' => TRUE,
     ];
 
+    $form['field_layouts']['settings_wrapper']['icon'] = $layout_plugin->getPluginDefinition()->getIcon();
+
     if ($layout_plugin instanceof PluginFormInterface) {
       $form['field_layouts']['settings_wrapper']['layout_settings'] = [];
       $subform_state = SubformState::createForSubform($form['field_layouts']['settings_wrapper']['layout_settings'], $form, $form_state);
diff --git a/core/modules/layout_builder/config/schema/layout_builder.schema.yml b/core/modules/layout_builder/config/schema/layout_builder.schema.yml
new file mode 100644
index 0000000..b870007
--- /dev/null
+++ b/core/modules/layout_builder/config/schema/layout_builder.schema.yml
@@ -0,0 +1,7 @@
+core.entity_view_display.*.*.*.third_party.layout_builder:
+  type: mapping
+  label: 'Per-view-mode Layout Builder settings'
+  mapping:
+    allow_custom:
+      type: boolean
+      label: 'Allow a customized layout'
diff --git a/core/modules/layout_builder/css/layout-builder.css b/core/modules/layout_builder/css/layout-builder.css
new file mode 100644
index 0000000..4c547e9
--- /dev/null
+++ b/core/modules/layout_builder/css/layout-builder.css
@@ -0,0 +1,59 @@
+.add-section {
+  width: 100%;
+  outline: 2px dashed #979797;
+  padding: 1.5em 0;
+  text-align: center;
+  margin-bottom: 1.5em;
+  transition: visually-hidden 2s ease-out, height 2s ease-in;
+}
+
+.layout-section {
+  margin-bottom: 1.5em;
+}
+
+.layout-section .layout-builder--layout__region {
+  outline: 2px dashed #2f91da;
+  padding: 1.5em 0;
+}
+
+.layout-section .layout-builder--layout__region .add-block {
+  text-align: center;
+}
+
+.layout-section .remove-section {
+  position: relative;
+  background: url(../../../misc/icons/bebebe/ex.svg) #ffffff center center / 16px 16px no-repeat;
+  border: 1px solid #cccccc;
+  box-sizing: border-box;
+  font-size: 1rem;
+  padding: 0;
+  height: 26px;
+  width: 26px;
+  white-space: nowrap;
+  text-indent: -9999px;
+  display: inline-block;
+  border-radius: 26px;
+  margin-left: -10px;
+}
+
+.layout-section .remove-section:hover {
+  background-image: url(../../../misc/icons/787878/ex.svg);
+}
+
+#drupal-off-canvas details.layout-selection {
+  background-color: transparent;
+}
+
+#drupal-off-canvas details.layout-selection summary {
+  margin-bottom: 1em;
+}
+
+#drupal-off-canvas details.layout-selection li {
+  display: block;
+  padding-bottom: 1em;
+}
+
+#drupal-off-canvas details.layout-selection li a {
+  display: block;
+  padding-top: 0.55em;
+}
diff --git a/core/modules/layout_builder/js/layout-builder.es6.js b/core/modules/layout_builder/js/layout-builder.es6.js
new file mode 100644
index 0000000..150f9a6
--- /dev/null
+++ b/core/modules/layout_builder/js/layout-builder.es6.js
@@ -0,0 +1,38 @@
+(($, { ajax, behaviors }) => {
+  behaviors.layoutBuilder = {
+    attach(context) {
+      $(context).find('.layout-builder--layout__region').sortable({
+        items: '> .draggable',
+        connectWith: '.layout-builder--layout__region',
+
+        /**
+         * Updates the layout with the new position of the block.
+         *
+         * @param {jQuery.Event} event
+         *   The jQuery Event object.
+         * @param {Object} ui
+         *   An object containing information about the item being sorted.
+         */
+        update(event, ui) {
+          const data = {
+            region_to: $(this).data('region'),
+            block_uuid: ui.item.data('layout-block-uuid'),
+            delta_to: ui.item.closest('[data-layout-delta]').data('layout-delta'),
+            preceding_block_uuid: ui.item.prev('[data-layout-block-uuid]').data('layout-block-uuid'),
+          };
+
+          // Only process if the item was moved from one region to another.
+          if (ui.sender) {
+            data.region_from = ui.sender.data('region');
+            data.delta_from = ui.sender.closest('[data-layout-delta]').data('layout-delta');
+
+            ajax({
+              url: ui.item.closest('[data-layout-update-url]').data('layout-update-url'),
+              submit: data,
+            }).execute();
+          }
+        },
+      });
+    },
+  };
+})(jQuery, Drupal);
diff --git a/core/modules/layout_builder/js/layout-builder.js b/core/modules/layout_builder/js/layout-builder.js
new file mode 100644
index 0000000..e9f6558
--- /dev/null
+++ b/core/modules/layout_builder/js/layout-builder.js
@@ -0,0 +1,39 @@
+/**
+* DO NOT EDIT THIS FILE.
+* See the following change record for more information,
+* https://www.drupal.org/node/2815083
+* @preserve
+**/
+
+(function ($, _ref) {
+  var ajax = _ref.ajax,
+      behaviors = _ref.behaviors;
+
+  behaviors.layoutBuilder = {
+    attach: function attach(context) {
+      $(context).find('.layout-builder--layout__region').sortable({
+        items: '> .draggable',
+        connectWith: '.layout-builder--layout__region',
+
+        update: function update(event, ui) {
+          var data = {
+            region_to: $(this).data('region'),
+            block_uuid: ui.item.data('layout-block-uuid'),
+            delta_to: ui.item.closest('[data-layout-delta]').data('layout-delta'),
+            preceding_block_uuid: ui.item.prev('[data-layout-block-uuid]').data('layout-block-uuid')
+          };
+
+          if (ui.sender) {
+            data.region_from = ui.sender.data('region');
+            data.delta_from = ui.sender.closest('[data-layout-delta]').data('layout-delta');
+
+            ajax({
+              url: ui.item.closest('[data-layout-update-url]').data('layout-update-url'),
+              submit: data
+            }).execute();
+          }
+        }
+      });
+    }
+  };
+})(jQuery, Drupal);
\ No newline at end of file
diff --git a/core/modules/layout_builder/layout_builder.info.yml b/core/modules/layout_builder/layout_builder.info.yml
new file mode 100644
index 0000000..7947421
--- /dev/null
+++ b/core/modules/layout_builder/layout_builder.info.yml
@@ -0,0 +1,10 @@
+name: 'Layout Builder'
+type: module
+description: 'Provides layout building utility.'
+package: Core (Experimental)
+version: VERSION
+core: 8.x
+dependencies:
+  - layout_discovery
+  # @todo Remove dependency once https://www.drupal.org/node/2784443 is in.
+  - settings_tray
diff --git a/core/modules/layout_builder/layout_builder.libraries.yml b/core/modules/layout_builder/layout_builder.libraries.yml
new file mode 100644
index 0000000..9c17391
--- /dev/null
+++ b/core/modules/layout_builder/layout_builder.libraries.yml
@@ -0,0 +1,10 @@
+drupal.layout_builder:
+  version: VERSION
+  css:
+    theme:
+      css/layout-builder.css: {}
+  js:
+    js/layout-builder.js: {}
+  dependencies:
+    - core/jquery.ui.sortable
+    - settings_tray/drupal.off_canvas
diff --git a/core/modules/layout_builder/layout_builder.links.contextual.yml b/core/modules/layout_builder/layout_builder.links.contextual.yml
new file mode 100644
index 0000000..67952a1
--- /dev/null
+++ b/core/modules/layout_builder/layout_builder.links.contextual.yml
@@ -0,0 +1,19 @@
+layout_builder_block_configure:
+  title: 'Configure'
+  route_name: 'layout_builder.configure_block'
+  group: 'layout_builder_block'
+  options:
+    attributes:
+      class: ['use-ajax']
+      data-dialog-type: dialog
+      data-dialog-renderer: off_canvas
+
+layout_builder_block_remove:
+  title: 'Remove block'
+  route_name: 'layout_builder.remove_block'
+  group: 'layout_builder_block'
+  options:
+    attributes:
+      class: ['use-ajax']
+      data-dialog-type: dialog
+      data-dialog-renderer: off_canvas
diff --git a/core/modules/layout_builder/layout_builder.links.task.yml b/core/modules/layout_builder/layout_builder.links.task.yml
new file mode 100644
index 0000000..b003d77
--- /dev/null
+++ b/core/modules/layout_builder/layout_builder.links.task.yml
@@ -0,0 +1,2 @@
+layout_builder_ui:
+  deriver: '\Drupal\layout_builder\Plugin\Derivative\LayoutBuilderLocalTaskDeriver'
diff --git a/core/modules/layout_builder/layout_builder.module b/core/modules/layout_builder/layout_builder.module
new file mode 100644
index 0000000..3bf7378
--- /dev/null
+++ b/core/modules/layout_builder/layout_builder.module
@@ -0,0 +1,145 @@
+<?php
+
+/**
+ * @file
+ * Provides hook implementations for Layout Builder.
+ */
+
+use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\field\Entity\FieldConfig;
+use Drupal\field\Entity\FieldStorageConfig;
+
+/**
+ * Implements hook_help().
+ */
+function layout_builder_help($route_name) {
+  switch ($route_name) {
+    case 'help.page.layout_builder':
+      $output = '<h3>' . t('About') . '</h3>';
+      $output .= '<p>' . t('Layout Builder provides layout building utility, surprisingly.') . '</p>';
+      $output .= '<p>' . t('For more information, see the <a href=":layout-builder-documentation">online documentation for the Layout Builder module</a>.', [':layout-builder-documentation' => 'https://www.drupal.org/docs/8/core/modules/layout_builder']) . '</p>';
+      return $output;
+  }
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter() for \Drupal\field_ui\Form\EntityViewDisplayEditForm.
+ */
+function layout_builder_form_entity_view_display_edit_form_alter(&$form, FormStateInterface $form_state) {
+  /** @var \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display */
+  $display = $form_state->getFormObject()->getEntity();
+  $entity_type = \Drupal::entityTypeManager()->getDefinition($display->getTargetEntityTypeId());
+
+  // Remove layout_builder__layout both visually and from the #fields handling.
+  // This prevents any interaction with this field. It is rendered directly
+  // in layout_builder_entity_view_display_alter().
+  unset($form['fields']['layout_builder__layout']);
+  unset($form['#fields'][array_search('layout_builder__layout', $form['#fields'])]);
+
+  $form['layout'] = [
+    '#type' => 'details',
+    '#open' => TRUE,
+    '#title' => t('Layout options'),
+    '#tree' => TRUE,
+  ];
+  $form['layout']['allow_custom'] = [
+    '#type' => 'checkbox',
+    '#title' => t('Allow each @entity to have its layout customized.', [
+      '@entity' => $entity_type->getSingularLabel(),
+    ]),
+    '#default_value' => $display->getThirdPartySetting('layout_builder', 'allow_custom', FALSE),
+  ];
+
+  // The submit handler should run before the entity is saved by the form.
+  array_unshift($form['actions']['submit']['#submit'], 'layout_builder_form_entity_view_display_edit_submit');
+}
+
+/**
+ * Form submission handler for layout options on the entity view display form.
+ *
+ * @see layout_builder_form_entity_view_display_edit_form_alter()
+ */
+function layout_builder_form_entity_view_display_edit_submit(&$form, FormStateInterface $form_state) {
+  /** @var \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display */
+  $display = $form_state->getFormObject()->getEntity();
+
+  $original_value = $display->getThirdPartySetting('layout_builder', 'allow_custom', FALSE);
+  $allow_custom = $form_state->getValue(['layout', 'allow_custom'], FALSE);
+
+  // Only continue if the value has changed.
+  if ($original_value !== $allow_custom) {
+    $display->setThirdPartySetting('layout_builder', 'allow_custom', $allow_custom);
+    $entity_type_id = $display->getTargetEntityTypeId();
+    $bundle = $display->getTargetBundle();
+
+    if ($allow_custom) {
+      layout_builder_add_layout_section_field($entity_type_id, $bundle);
+    }
+    elseif ($field = FieldConfig::loadByName($entity_type_id, $bundle, 'layout_builder__layout')) {
+      $field->delete();
+    }
+  }
+}
+
+/**
+ * Adds a layout section field to a given bundle.
+ *
+ * @param string $entity_type_id
+ *   The entity type ID.
+ * @param string $bundle
+ *   The bundle.
+ * @param string $field_name
+ *   (optional) The name for the layout section field. Defaults to
+ *   'layout_builder__layout'.
+ *
+ * @return \Drupal\field\FieldConfigInterface
+ *   A layout section field.
+ */
+function layout_builder_add_layout_section_field($entity_type_id, $bundle, $field_name = 'layout_builder__layout') {
+  $field = FieldConfig::loadByName($entity_type_id, $bundle, $field_name);
+  if (!$field) {
+    $field_storage = FieldStorageConfig::loadByName($entity_type_id, $field_name);
+    if (!$field_storage) {
+      $field_storage = FieldStorageConfig::create([
+        'entity_type' => $entity_type_id,
+        'field_name' => $field_name,
+        'type' => 'layout_section',
+      ]);
+      $field_storage->save();
+    }
+
+    $field = FieldConfig::create([
+      'field_storage' => $field_storage,
+      'bundle' => $bundle,
+      'label' => t('Layout'),
+    ]);
+    $field->save();
+  }
+  return $field;
+}
+
+/**
+ * Implements hook_entity_view_display_alter().
+ */
+function layout_builder_entity_view_display_alter(EntityViewDisplayInterface $display, array $context) {
+  // @todo Expand to work for all view modes in
+  //   https://www.drupal.org/node/2907413.
+  if (!in_array($context['view_mode'], ['full', 'default'])) {
+    return;
+  }
+
+  if ($display->getThirdPartySetting('layout_builder', 'allow_custom', FALSE)) {
+    /** @var \Drupal\Core\Field\FieldDefinitionInterface[] $field_definitions */
+    $field_definitions = \Drupal::service('entity_field.manager')->getFieldDefinitions($display->getTargetEntityTypeId(), $display->getTargetBundle());
+    // Remove all display-configurable fields.
+    foreach (array_keys($display->getComponents()) as $name) {
+      if (isset($field_definitions[$name]) && $field_definitions[$name]->isDisplayConfigurable('view')) {
+        $display->removeComponent($name);
+      }
+    }
+
+    // Force the layout to render with no label.
+    $display->setComponent('layout_builder__layout', ['label' => 'hidden']);
+  }
+}
diff --git a/core/modules/layout_builder/layout_builder.permissions.yml b/core/modules/layout_builder/layout_builder.permissions.yml
new file mode 100644
index 0000000..0d66577
--- /dev/null
+++ b/core/modules/layout_builder/layout_builder.permissions.yml
@@ -0,0 +1,3 @@
+configure any layout:
+  title: 'Configure any layout'
+  restrict access: true
diff --git a/core/modules/layout_builder/layout_builder.routing.yml b/core/modules/layout_builder/layout_builder.routing.yml
new file mode 100644
index 0000000..718e678
--- /dev/null
+++ b/core/modules/layout_builder/layout_builder.routing.yml
@@ -0,0 +1,122 @@
+layout_builder.choose_section:
+  path: '/layout_builder/choose/section/{entity_type_id}/{entity}/{delta}'
+  defaults:
+   _controller: '\Drupal\layout_builder\Controller\ChooseSectionController::build'
+  requirements:
+    _permission: 'configure any layout'
+  options:
+    _admin_route: TRUE
+    parameters:
+      entity:
+        type: entity:{entity_type_id}
+        layout_builder_tempstore: TRUE
+
+layout_builder.add_section:
+  path: '/layout_builder/add/section/{entity_type_id}/{entity}/{delta}/{plugin_id}'
+  defaults:
+    _controller: '\Drupal\layout_builder\Controller\AddSectionController::build'
+  requirements:
+    _permission: 'configure any layout'
+  options:
+    _admin_route: TRUE
+    parameters:
+      entity:
+        type: entity:{entity_type_id}
+        layout_builder_tempstore: TRUE
+
+layout_builder.configure_section:
+  path: '/layout_builder/configure/section/{entity_type_id}/{entity}/{delta}/{plugin_id}'
+  defaults:
+    _title: 'Configure section'
+    _form: '\Drupal\layout_builder\Form\ConfigureSectionForm'
+    plugin_id: null
+  requirements:
+    _permission: 'configure any layout'
+  options:
+    _admin_route: TRUE
+    parameters:
+      entity:
+        type: entity:{entity_type_id}
+        layout_builder_tempstore: TRUE
+
+layout_builder.remove_section:
+  path: '/layout_builder/remove/section/{entity_type_id}/{entity}/{delta}'
+  defaults:
+    _form: '\Drupal\layout_builder\Form\RemoveSectionForm'
+  requirements:
+    _permission: 'configure any layout'
+  options:
+    _admin_route: TRUE
+    parameters:
+      entity:
+        type: entity:{entity_type_id}
+        layout_builder_tempstore: TRUE
+
+layout_builder.choose_block:
+  path: '/layout_builder/choose/block/{entity_type_id}/{entity}/{delta}/{region}'
+  defaults:
+    _controller: '\Drupal\layout_builder\Controller\ChooseBlockController::build'
+  requirements:
+    _permission: 'configure any layout'
+  options:
+    _admin_route: TRUE
+    parameters:
+      entity:
+        type: entity:{entity_type_id}
+        layout_builder_tempstore: TRUE
+
+layout_builder.add_block:
+  path: '/layout_builder/add/block/{entity_type_id}/{entity}/{delta}/{region}/{plugin_id}'
+  defaults:
+    _form: '\Drupal\layout_builder\Form\ConfigureBlockForm'
+  requirements:
+    _permission: 'configure any layout'
+  options:
+    _admin_route: TRUE
+    parameters:
+      entity:
+        type: entity:{entity_type_id}
+        layout_builder_tempstore: TRUE
+
+layout_builder.configure_block:
+  path: '/layout_builder/configure/block/{entity_type_id}/{entity}/{delta}/{region}/{uuid}'
+  defaults:
+    _form: '\Drupal\layout_builder\Form\ConfigureBlockForm'
+  requirements:
+    _permission: 'configure any layout'
+  options:
+    _admin_route: TRUE
+    parameters:
+      entity:
+        type: entity:{entity_type_id}
+        layout_builder_tempstore: TRUE
+
+layout_builder.remove_block:
+  path: '/layout_builder/remove/block/{entity_type_id}/{entity}/{delta}/{region}/{uuid}'
+  defaults:
+    _form: '\Drupal\layout_builder\Form\RemoveBlockForm'
+  requirements:
+    _permission: 'configure any layout'
+  options:
+    _admin_route: TRUE
+    parameters:
+      entity:
+        type: entity:{entity_type_id}
+        layout_builder_tempstore: TRUE
+
+layout_builder.move_block:
+  path: '/layout_builder/move/block/{entity_type_id}/{entity}'
+  defaults:
+    _controller: '\Drupal\layout_builder\Controller\MoveBlockController::build'
+  methods: [POST]
+  requirements:
+    _permission: 'configure any layout'
+  options:
+    _admin_route: TRUE
+    parameters:
+      entity:
+        type: entity:{entity_type_id}
+        layout_builder_tempstore: TRUE
+
+route_callbacks:
+  - 'layout_builder.routes:getRoutes'
diff --git a/core/modules/layout_builder/layout_builder.services.yml b/core/modules/layout_builder/layout_builder.services.yml
new file mode 100644
index 0000000..ba8e0cc
--- /dev/null
+++ b/core/modules/layout_builder/layout_builder.services.yml
@@ -0,0 +1,27 @@
+services:
+  layout_builder.builder:
+    class: Drupal\layout_builder\LayoutSectionBuilder
+    arguments: ['@current_user', '@plugin.manager.core.layout', '@plugin.manager.block', '@context.handler', '@context.repository']
+  layout_builder.tempstore_repository:
+    class: Drupal\layout_builder\LayoutTempstoreRepository
+    arguments: ['@user.shared_tempstore', '@entity_type.manager']
+  access_check.entity.layout:
+    class: Drupal\layout_builder\Access\LayoutSectionAccessCheck
+    arguments: ['@entity_type.manager']
+    tags:
+      - { name: access_check, applies_to: _has_layout_section }
+  layout_builder.routes:
+    class: Drupal\layout_builder\Routing\LayoutBuilderRoutes
+    arguments: ['@entity_type.manager']
+    tags:
+      - { name: event_subscriber }
+  layout_builder.route_enhancer:
+    class: Drupal\layout_builder\Routing\LayoutBuilderRouteEnhancer
+    arguments: ['@entity_type.manager']
+    tags:
+      - { name: route_enhancer }
+  layout_builder.param_converter:
+    class: Drupal\layout_builder\Routing\LayoutTempstoreParamConverter
+    arguments: ['@entity.manager', '@layout_builder.tempstore_repository']
+    tags:
+      - { name: paramconverter, priority: 10 }
diff --git a/core/modules/layout_builder/src/Access/LayoutSectionAccessCheck.php b/core/modules/layout_builder/src/Access/LayoutSectionAccessCheck.php
new file mode 100644
index 0000000..8525eea
--- /dev/null
+++ b/core/modules/layout_builder/src/Access/LayoutSectionAccessCheck.php
@@ -0,0 +1,63 @@
+<?php
+
+namespace Drupal\layout_builder\Access;
+
+use Drupal\Core\Access\AccessResult;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Entity\FieldableEntityInterface;
+use Drupal\Core\Routing\Access\AccessInterface;
+use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\Session\AccountInterface;
+
+/**
+ * Provides an access check for the Layout Builder UI.
+ */
+class LayoutSectionAccessCheck implements AccessInterface {
+
+  /**
+   * The entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * Constructs a new LayoutSectionAccessCheck.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The entity type manager.
+   */
+  public function __construct(EntityTypeManagerInterface $entity_type_manager) {
+    $this->entityTypeManager = $entity_type_manager;
+  }
+
+  /**
+   * Checks routing access to layout for the entity.
+   *
+   * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
+   *   The current route match.
+   * @param \Drupal\Core\Session\AccountInterface $account
+   *   The currently logged in account.
+   *
+   * @return \Drupal\Core\Access\AccessResultInterface
+   *   The access result.
+   */
+  public function access(RouteMatchInterface $route_match, AccountInterface $account) {
+    $entity = $route_match->getParameter('entity');
+    // If we don't have an entity, forbid access.
+    if (empty($entity)) {
+      return AccessResult::forbidden()->addCacheContexts(['route']);
+    }
+
+    // If the entity isn't fieldable, forbid access.
+    if (!$entity instanceof FieldableEntityInterface || !$entity->hasField('layout_builder__layout')) {
+      $access = AccessResult::forbidden();
+    }
+    else {
+      $access = AccessResult::allowedIf($account->hasPermission('configure any layout'));
+    }
+
+    return $access->addCacheableDependency($entity);
+  }
+
+}
diff --git a/core/modules/layout_builder/src/Controller/AddSectionController.php b/core/modules/layout_builder/src/Controller/AddSectionController.php
new file mode 100644
index 0000000..550442a
--- /dev/null
+++ b/core/modules/layout_builder/src/Controller/AddSectionController.php
@@ -0,0 +1,70 @@
+<?php
+
+namespace Drupal\layout_builder\Controller;
+
+use Drupal\Core\Ajax\AjaxResponse;
+use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\layout_builder\LayoutTempstoreRepositoryInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Returns responses for Layout Builder routes.
+ */
+class AddSectionController implements ContainerInjectionInterface {
+
+  use LayoutRebuildTrait;
+
+  /**
+   * The layout tempstore repository.
+   *
+   * @var \Drupal\layout_builder\LayoutTempstoreRepositoryInterface
+   */
+  protected $layoutTempstoreRepository;
+
+  /**
+   * AddSectionController constructor.
+   *
+   * @param \Drupal\layout_builder\LayoutTempstoreRepositoryInterface $layout_tempstore_repository
+   *   The layout tempstore repository.
+   */
+  public function __construct(LayoutTempstoreRepositoryInterface $layout_tempstore_repository) {
+    $this->layoutTempstoreRepository = $layout_tempstore_repository;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('layout_builder.tempstore_repository')
+    );
+  }
+
+  /**
+   * Add the layout to the entity field in a tempstore.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The entity.
+   * @param int $delta
+   *   The delta of the section to splice.
+   * @param string $plugin_id
+   *   The plugin ID of the layout to add.
+   *
+   * @return \Drupal\Core\Ajax\AjaxResponse
+   *   The render array.
+   */
+  public function build(EntityInterface $entity, $delta, $plugin_id) {
+    /** @var \Drupal\layout_builder\Field\LayoutSectionItemListInterface $field_list */
+    $field_list = $entity->layout_builder__layout;
+    $field_list->addItem($delta, [
+      'layout' => $plugin_id,
+      'layout_settings' => [],
+      'section' => [],
+    ]);
+
+    $this->layoutTempstoreRepository->set($entity);
+    return $this->rebuildAndClose(new AjaxResponse(), $entity);
+  }
+
+}
diff --git a/core/modules/layout_builder/src/Controller/ChooseBlockController.php b/core/modules/layout_builder/src/Controller/ChooseBlockController.php
new file mode 100644
index 0000000..06ea8ae
--- /dev/null
+++ b/core/modules/layout_builder/src/Controller/ChooseBlockController.php
@@ -0,0 +1,90 @@
+<?php
+
+namespace Drupal\layout_builder\Controller;
+
+use Drupal\Core\Block\BlockManagerInterface;
+use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Url;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Returns responses for Layout Builder routes.
+ */
+class ChooseBlockController implements ContainerInjectionInterface {
+
+  /**
+   * The block manager.
+   *
+   * @var \Drupal\Core\Block\BlockManagerInterface
+   */
+  protected $blockManager;
+
+  /**
+   * ChooseBlockController constructor.
+   *
+   * @param \Drupal\Core\Block\BlockManagerInterface $block_manager
+   *   The block manager.
+   */
+  public function __construct(BlockManagerInterface $block_manager) {
+    $this->blockManager = $block_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('plugin.manager.block')
+    );
+  }
+
+  /**
+   * Provides the UI for choosing a new block.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The entity.
+   * @param int $delta
+   *   The delta of the section to splice.
+   * @param string $region
+   *   The region the block is going in.
+   *
+   * @return array
+   *   A render array.
+   */
+  public function build(EntityInterface $entity, $delta, $region) {
+    $build['#type'] = 'container';
+    $build['#attributes']['class'][] = 'block-categories';
+
+    foreach ($this->blockManager->getGroupedDefinitions() as $category => $blocks) {
+      $build[$category]['#type'] = 'details';
+      $build[$category]['#open'] = TRUE;
+      $build[$category]['#title'] = $category;
+      $build[$category]['links'] = [
+        '#type' => 'table',
+      ];
+      foreach ($blocks as $block_id => $block) {
+        $build[$category]['links'][]['data'] = [
+          '#type' => 'link',
+          '#title' => $block['admin_label'],
+          '#url' => Url::fromRoute('layout_builder.add_block',
+            [
+              'entity_type_id' => $entity->getEntityTypeId(),
+              'entity' => $entity->id(),
+              'delta' => $delta,
+              'region' => $region,
+              'plugin_id' => $block_id,
+            ]
+          ),
+          '#attributes' => [
+            'class' => ['use-ajax'],
+            'data-dialog-type' => 'dialog',
+            'data-dialog-renderer' => 'off_canvas',
+          ],
+        ];
+      }
+    }
+    return $build;
+  }
+
+}
diff --git a/core/modules/layout_builder/src/Controller/ChooseSectionController.php b/core/modules/layout_builder/src/Controller/ChooseSectionController.php
new file mode 100644
index 0000000..7fb138c
--- /dev/null
+++ b/core/modules/layout_builder/src/Controller/ChooseSectionController.php
@@ -0,0 +1,112 @@
+<?php
+
+namespace Drupal\layout_builder\Controller;
+
+use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Layout\LayoutPluginManagerInterface;
+use Drupal\Core\Plugin\PluginFormInterface;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\Core\Url;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Returns responses for Layout Builder routes.
+ */
+class ChooseSectionController implements ContainerInjectionInterface {
+
+  use StringTranslationTrait;
+
+  /**
+   * The layout manager.
+   *
+   * @var \Drupal\Core\Layout\LayoutPluginManagerInterface
+   */
+  protected $layoutManager;
+
+  /**
+   * ChooseSectionController constructor.
+   *
+   * @param \Drupal\Core\Layout\LayoutPluginManagerInterface $layout_manager
+   *   The layout manager.
+   */
+  public function __construct(LayoutPluginManagerInterface $layout_manager) {
+    $this->layoutManager = $layout_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('plugin.manager.core.layout')
+    );
+  }
+
+  /**
+   * Choose a layout plugin to add as a section.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The entity.
+   * @param int $delta
+   *   The delta of the section to splice.
+   *
+   * @return array
+   *   The render array.
+   */
+  public function build(EntityInterface $entity, $delta) {
+    $output = [];
+    $items = [];
+    foreach ($this->layoutManager->getDefinitions() as $plugin_id => $definition) {
+      $layout = $this->layoutManager->createInstance($plugin_id);
+      $items[] = [
+        'label' => [
+          '#type' => 'link',
+          '#title' => [
+            $definition->getIcon(60, 80, 1, 3),
+            [
+              '#type' => 'container',
+              '#children' => $definition->getLabel(),
+            ],
+          ],
+          '#url' => Url::fromRoute(
+            $layout instanceof PluginFormInterface ? 'layout_builder.configure_section' : 'layout_builder.add_section',
+            [
+              'entity_type_id' => $entity->getEntityTypeId(),
+              'entity' => $entity->id(),
+              'delta' => $delta,
+              'plugin_id' => $plugin_id,
+            ]
+          ),
+          '#attributes' => [
+            'class' => ['use-ajax'],
+            'data-dialog-type' => 'dialog',
+            'data-dialog-renderer' => 'off_canvas',
+          ],
+        ],
+      ];
+    }
+    $output['layouts'] = [
+      '#type' => 'details',
+      '#title' => $this->t('Basic Layouts'),
+      '#open' => TRUE,
+      '#attributes' => [
+        'class' => [
+          'layout-selection',
+        ],
+      ],
+      'list' => [
+        '#theme' => 'item_list',
+        '#items' => $items,
+        '#attributes' => [
+          'class' => [
+            'layout-list',
+          ],
+        ],
+      ],
+    ];
+
+    return $output;
+  }
+
+}
diff --git a/core/modules/layout_builder/src/Controller/LayoutBuilderController.php b/core/modules/layout_builder/src/Controller/LayoutBuilderController.php
new file mode 100644
index 0000000..48f8a49
--- /dev/null
+++ b/core/modules/layout_builder/src/Controller/LayoutBuilderController.php
@@ -0,0 +1,306 @@
+<?php
+
+namespace Drupal\layout_builder\Controller;
+
+use Drupal\Core\Block\BlockManagerInterface;
+use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Layout\LayoutPluginManagerInterface;
+use Drupal\Core\Plugin\PluginFormInterface;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\Core\Url;
+use Drupal\layout_builder\LayoutSectionBuilder;
+use Drupal\layout_builder\LayoutSectionItemInterface;
+use Drupal\layout_builder\LayoutTempstoreRepositoryInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpFoundation\RedirectResponse;
+
+/**
+ * Returns responses for Layout Builder routes.
+ */
+class LayoutBuilderController implements ContainerInjectionInterface {
+
+  use StringTranslationTrait;
+
+  /**
+   * The layout builder.
+   *
+   * @var \Drupal\layout_builder\LayoutSectionBuilder
+   */
+  protected $builder;
+
+  /**
+   * The layout manager.
+   *
+   * @var \Drupal\Core\Layout\LayoutPluginManagerInterface
+   */
+  protected $layoutManager;
+
+  /**
+   * The block manager.
+   *
+   * @var \Drupal\Core\Block\BlockManagerInterface
+   */
+  protected $blockManager;
+
+  /**
+   * The layout tempstore repository.
+   *
+   * @var \Drupal\layout_builder\LayoutTempstoreRepositoryInterface
+   */
+  protected $layoutTempstoreRepository;
+
+  /**
+   * LayoutController constructor.
+   *
+   * @param \Drupal\layout_builder\LayoutSectionBuilder $builder
+   *   The layout section builder.
+   * @param \Drupal\Core\Layout\LayoutPluginManagerInterface $layout_manager
+   *   The layout manager.
+   * @param \Drupal\Core\Block\BlockManagerInterface $block_manager
+   *   The block manager.
+   * @param \Drupal\layout_builder\LayoutTempstoreRepositoryInterface $layout_tempstore_repository
+   *   The layout tempstore repository.
+   */
+  public function __construct(LayoutSectionBuilder $builder, LayoutPluginManagerInterface $layout_manager, BlockManagerInterface $block_manager, LayoutTempstoreRepositoryInterface $layout_tempstore_repository) {
+    $this->builder = $builder;
+    $this->layoutManager = $layout_manager;
+    $this->blockManager = $block_manager;
+    $this->layoutTempstoreRepository = $layout_tempstore_repository;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('layout_builder.builder'),
+      $container->get('plugin.manager.core.layout'),
+      $container->get('plugin.manager.block'),
+      $container->get('layout_builder.tempstore_repository')
+    );
+  }
+
+  /**
+   * Provides a title callback.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The entity.
+   *
+   * @return string
+   *   The title for the layout page.
+   */
+  public function title(EntityInterface $entity) {
+    return $this->t('Edit layout for %label', ['%label' => $entity->label()]);
+  }
+
+  /**
+   * Renders the Layout UI.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The entity.
+   *
+   * @return array
+   *   A render array.
+   */
+  public function layout(EntityInterface $entity) {
+    $entity_id = $entity->id();
+    $entity_type_id = $entity->getEntityTypeId();
+
+    $output = [];
+    $count = 0;
+    /** @var \Drupal\layout_builder\LayoutSectionItemInterface $item */
+    foreach ($entity->layout_builder__layout as $item) {
+      $output[] = $this->buildAddSectionLink($entity_type_id, $entity_id, $count);
+      $output[] = $this->buildAdministrativeSection($item, $entity, $count);
+      $count++;
+    }
+    $output[] = $this->buildAddSectionLink($entity_type_id, $entity_id, $count);
+    $output['#attached']['library'][] = 'layout_builder/drupal.layout_builder';
+    $output['#type'] = 'container';
+    $output['#attributes']['id'] = 'layout-builder';
+    // Mark this UI as uncacheable.
+    $output['#cache']['max-age'] = 0;
+    return $output;
+  }
+
+  /**
+   * Builds a link to add a new section at a given delta.
+   *
+   * @param string $entity_type_id
+   *   The entity type.
+   * @param string $entity_id
+   *   The entity ID.
+   * @param int $delta
+   *   The delta of the section to splice.
+   *
+   * @return array
+   *   A render array for a link.
+   */
+  protected function buildAddSectionLink($entity_type_id, $entity_id, $delta) {
+    return [
+      'link' => [
+        '#type' => 'link',
+        '#title' => $this->t('Add Section'),
+        '#url' => Url::fromRoute('layout_builder.choose_section',
+          [
+            'entity_type_id' => $entity_type_id,
+            'entity' => $entity_id,
+            'delta' => $delta,
+          ],
+          [
+            'attributes' => [
+              'class' => ['use-ajax'],
+              'data-dialog-type' => 'dialog',
+              'data-dialog-renderer' => 'off_canvas',
+            ],
+          ]
+        ),
+      ],
+      '#type' => 'container',
+      '#attributes' => [
+        'class' => ['add-section'],
+      ],
+    ];
+  }
+
+  /**
+   * Builds the render array for the layout section while editing.
+   *
+   * @param \Drupal\layout_builder\LayoutSectionItemInterface $item
+   *   The layout section item.
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The entity.
+   * @param int $delta
+   *   The delta of the section.
+   *
+   * @return array
+   *   The render array for a given section.
+   */
+  protected function buildAdministrativeSection(LayoutSectionItemInterface $item, EntityInterface $entity, $delta) {
+    $entity_type_id = $entity->getEntityTypeId();
+    $entity_id = $entity->id();
+
+    $layout = $this->layoutManager->createInstance($item->layout, $item->layout_settings);
+    $build = $this->builder->buildSectionFromLayout($layout, $item->section);
+    $layout_definition = $layout->getPluginDefinition();
+
+    foreach ($layout_definition->getRegions() as $region => $info) {
+      if (!empty($build[$region])) {
+        foreach ($build[$region] as $uuid => $block) {
+          $build[$region][$uuid]['#attributes']['class'][] = 'draggable';
+          $build[$region][$uuid]['#attributes']['data-layout-block-uuid'] = $uuid;
+          $build[$region][$uuid]['#contextual_links'] = [
+            'layout_builder_block' => [
+              'route_parameters' => [
+                'entity_type_id' => $entity_type_id,
+                'entity' => $entity_id,
+                'delta' => $delta,
+                'region' => $region,
+                'uuid' => $uuid,
+              ],
+            ],
+          ];
+        }
+      }
+
+      $build[$region]['layout_builder_add_block']['link'] = [
+        '#type' => 'link',
+        '#title' => $this->t('Add Block'),
+        '#url' => Url::fromRoute('layout_builder.choose_block',
+          [
+            'entity_type_id' => $entity_type_id,
+            'entity' => $entity_id,
+            'delta' => $delta,
+            'region' => $region,
+          ],
+          [
+            'attributes' => [
+              'class' => ['use-ajax'],
+              'data-dialog-type' => 'dialog',
+              'data-dialog-renderer' => 'off_canvas',
+            ],
+          ]
+        ),
+      ];
+      $build[$region]['layout_builder_add_block']['#type'] = 'container';
+      $build[$region]['layout_builder_add_block']['#attributes'] = ['class' => ['add-block']];
+      $build[$region]['#attributes']['data-region'] = $region;
+      $build[$region]['#attributes']['class'][] = 'layout-builder--layout__region';
+    }
+
+    $build['#attributes']['data-layout-update-url'] = Url::fromRoute('layout_builder.move_block', [
+      'entity_type_id' => $entity_type_id,
+      'entity' => $entity_id,
+    ])->toString();
+    $build['#attributes']['data-layout-delta'] = $delta;
+    $build['#attributes']['class'][] = 'layout-builder--layout';
+
+    return [
+      '#type' => 'container',
+      '#attributes' => [
+        'class' => ['layout-section'],
+      ],
+      'configure' => [
+        '#type' => 'link',
+        '#title' => $this->t('Configure section'),
+        '#access' => $layout instanceof PluginFormInterface,
+        '#url' => Url::fromRoute('layout_builder.configure_section', [
+          'entity_type_id' => $entity_type_id,
+          'entity' => $entity_id,
+          'delta' => $delta,
+        ]),
+        '#attributes' => [
+          'class' => ['use-ajax', 'configure-section'],
+          'data-dialog-type' => 'dialog',
+          'data-dialog-renderer' => 'off_canvas',
+        ],
+      ],
+      'remove' => [
+        '#type' => 'link',
+        '#title' => $this->t('Remove section'),
+        '#url' => Url::fromRoute('layout_builder.remove_section', [
+          'entity_type_id' => $entity_type_id,
+          'entity' => $entity_id,
+          'delta' => $delta,
+        ]),
+        '#attributes' => [
+          'class' => ['use-ajax', 'remove-section'],
+          'data-dialog-type' => 'dialog',
+          'data-dialog-renderer' => 'off_canvas',
+        ],
+      ],
+      'layout-section' => $build,
+    ];
+  }
+
+  /**
+   * Saves the layout.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The entity.
+   *
+   * @return \Symfony\Component\HttpFoundation\RedirectResponse
+   *   A redirect response.
+   */
+  public function saveLayout(EntityInterface $entity) {
+    $entity->save();
+    $this->layoutTempstoreRepository->delete($entity);
+    return new RedirectResponse($entity->toUrl()->setAbsolute()->toString());
+  }
+
+  /**
+   * Cancels the layout.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The entity.
+   *
+   * @return \Symfony\Component\HttpFoundation\RedirectResponse
+   *   A redirect response.
+   */
+  public function cancelLayout(EntityInterface $entity) {
+    $this->layoutTempstoreRepository->delete($entity);
+    return new RedirectResponse($entity->toUrl()->setAbsolute()->toString());
+  }
+
+}
diff --git a/core/modules/layout_builder/src/Controller/LayoutRebuildTrait.php b/core/modules/layout_builder/src/Controller/LayoutRebuildTrait.php
new file mode 100644
index 0000000..547838f
--- /dev/null
+++ b/core/modules/layout_builder/src/Controller/LayoutRebuildTrait.php
@@ -0,0 +1,125 @@
+<?php
+
+namespace Drupal\layout_builder\Controller;
+
+use Drupal\Core\Ajax\AjaxResponse;
+use Drupal\Core\Ajax\CloseDialogCommand;
+use Drupal\Core\Ajax\RedirectCommand;
+use Drupal\Core\Ajax\ReplaceCommand;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Url;
+
+/**
+ * Provides AJAX responses to rebuild the Layout Builder.
+ */
+trait LayoutRebuildTrait {
+
+  /**
+   * Submit form dialog #ajax callback.
+   *
+   * @param array $form
+   *   An associative array containing the structure of the form.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   *
+   * @return \Drupal\Core\Ajax\AjaxResponse
+   *   An AJAX response that display validation error messages or redirects
+   *   to a URL
+   */
+  public function ajaxSubmit(array &$form, FormStateInterface $form_state) {
+    if ($form_state->hasAnyErrors()) {
+      $form['status_messages'] = [
+        '#type' => 'status_messages',
+        '#weight' => -1000,
+      ];
+      $response = new AjaxResponse();
+      $response->addCommand(new ReplaceCommand('[data-drupal-selector="' . $form['#attributes']['data-drupal-selector'] . '"]', $form));
+    }
+    else {
+      $response = $this->rebuildAndClose(new AjaxResponse(), $this->entity);
+    }
+    return $response;
+  }
+
+  /**
+   * Rebuilds the layout.
+   *
+   * @param \Drupal\Core\Ajax\AjaxResponse $response
+   *   The AJAX response.
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The entity.
+   *
+   * @return \Drupal\Core\Ajax\AjaxResponse
+   *   An AJAX response to either rebuild the layout and close the dialog, or
+   *   reload the page.
+   */
+  protected function rebuildAndClose(AjaxResponse $response, EntityInterface $entity) {
+    $response = $this->rebuildLayout($response, $entity);
+    $url = Url::fromRoute("entity.{$entity->getEntityTypeId()}.layout", [$entity->getEntityTypeId() => $entity->id()]);
+    if ($this->isDialog()) {
+      $response->addCommand(new CloseDialogCommand('#drupal-off-canvas'));
+    }
+    else {
+      $response->addCommand(new RedirectCommand($url->setAbsolute()->toString()));
+    }
+    return $response;
+  }
+
+  /**
+   * Rebuilds the layout.
+   *
+   * @param \Drupal\Core\Ajax\AjaxResponse $response
+   *   The AJAX response.
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The entity.
+   *
+   * @return \Drupal\Core\Ajax\AjaxResponse
+   *   An AJAX response to either rebuild the layout and close the dialog, or
+   *   reload the page.
+   */
+  protected function rebuildLayout(AjaxResponse $response, EntityInterface $entity) {
+    $layout_controller = $this->getClassResolver()->getInstanceFromDefinition(LayoutBuilderController::class);
+    $layout = $layout_controller->layout($entity);
+    $response->addCommand(new ReplaceCommand('#layout-builder', $layout));
+    return $response;
+  }
+
+  /**
+   * Determines if the current request is within a dialog.
+   *
+   * @return bool
+   *   TRUE if the current request is within a dialog, FALSE otherwise.
+   */
+  protected function isDialog() {
+    return $this->getRequest()->get(MainContentViewSubscriber::WRAPPER_FORMAT) === 'drupal_dialog.off_canvas';
+  }
+
+  /**
+   * Gets the request object.
+   *
+   * @return \Symfony\Component\HttpFoundation\Request
+   *   The request object.
+   */
+  protected function getRequest() {
+    if (!$this->requestStack) {
+      $this->requestStack = \Drupal::requestStack();
+    }
+    return $this->requestStack->getCurrentRequest();
+  }
+
+  /**
+   * Gets the class resolver.
+   *
+   * @return \Drupal\Core\DependencyInjection\ClassResolver
+   *   The class resolver.
+   */
+  protected function getClassResolver() {
+    if (!$this->classResolver) {
+      $this->classResolver = \Drupal::classResolver();
+    }
+    return $this->classResolver;
+  }
+
+}
diff --git a/core/modules/layout_builder/src/Controller/MoveBlockController.php b/core/modules/layout_builder/src/Controller/MoveBlockController.php
new file mode 100644
index 0000000..a30e4d3
--- /dev/null
+++ b/core/modules/layout_builder/src/Controller/MoveBlockController.php
@@ -0,0 +1,91 @@
+<?php
+
+namespace Drupal\layout_builder\Controller;
+
+use Drupal\Core\Ajax\AjaxResponse;
+use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\layout_builder\LayoutTempstoreRepositoryInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Returns responses for Layout Builder routes.
+ */
+class MoveBlockController implements ContainerInjectionInterface {
+
+  use LayoutRebuildTrait;
+
+  /**
+   * The layout tempstore repository.
+   *
+   * @var \Drupal\layout_builder\LayoutTempstoreRepositoryInterface
+   */
+  protected $layoutTempstoreRepository;
+
+  /**
+   * LayoutController constructor.
+   *
+   * @param \Drupal\layout_builder\LayoutTempstoreRepositoryInterface $layout_tempstore_repository
+   *   The layout tempstore repository.
+   */
+  public function __construct(LayoutTempstoreRepositoryInterface $layout_tempstore_repository) {
+    $this->layoutTempstoreRepository = $layout_tempstore_repository;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('layout_builder.tempstore_repository')
+    );
+  }
+
+  /**
+   * Moves a block to another region.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The entity.
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The request.
+   *
+   * @return \Drupal\Core\Ajax\AjaxResponse
+   *   An AJAX response.
+   */
+  public function build(EntityInterface $entity, Request $request) {
+    $data = $request->request->all();
+
+    /** @var \Drupal\layout_builder\LayoutSectionItemInterface $field */
+    $field = $entity->layout_builder__layout->get($data['delta_from']);
+    $values = $field->section;
+
+    $region_from = $data['region_from'];
+    $region_to = $data['region_to'];
+    $block_uuid = $data['block_uuid'];
+    $configuration = $values[$region_from][$block_uuid];
+    unset($values[$region_from][$block_uuid]);
+    $field->section = array_filter($values);
+
+    /** @var \Drupal\layout_builder\LayoutSectionItemInterface $field */
+    $field = $entity->layout_builder__layout->get($data['delta_to']);
+    $values = $field->section;
+    if (isset($data['preceding_block_uuid'])) {
+      $slice_id = array_search($data['preceding_block_uuid'], array_keys($values[$region_to]));
+      $before = array_slice($values[$region_to], 0, $slice_id + 1);
+      $after = array_slice($values[$region_to], $slice_id + 1);
+      $values[$region_to] = array_merge($before, [$block_uuid => $configuration], $after);
+    }
+    else {
+      if (empty($values[$region_to])) {
+        $values[$region_to] = [];
+      }
+      $values[$region_to] = array_merge([$block_uuid => $configuration], $values[$region_to]);
+    }
+    $field->section = array_filter($values);
+
+    $this->layoutTempstoreRepository->set($entity);
+    return $this->rebuildLayout(new AjaxResponse(), $entity);
+  }
+
+}
diff --git a/core/modules/layout_builder/src/Field/LayoutSectionItemList.php b/core/modules/layout_builder/src/Field/LayoutSectionItemList.php
new file mode 100644
index 0000000..3d31fa2
--- /dev/null
+++ b/core/modules/layout_builder/src/Field/LayoutSectionItemList.php
@@ -0,0 +1,30 @@
+<?php
+
+namespace Drupal\layout_builder\Field;
+
+use Drupal\Core\Field\FieldItemList;
+
+/**
+ * Defines a item list class for layout section fields.
+ *
+ * @see \Drupal\layout_builder\Plugin\Field\FieldType\LayoutSectionItem
+ */
+class LayoutSectionItemList extends FieldItemList implements LayoutSectionItemListInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function addItem($index, $value) {
+    if ($this->get($index)) {
+      $start = array_slice($this->list, 0, $index);
+      $end = array_slice($this->list, $index);
+      $item = $this->createItem($index, $value);
+      $this->list = array_merge($start, [$item], $end);
+    }
+    else {
+      $item = $this->appendItem($value);
+    }
+    return $item;
+  }
+
+}
diff --git a/core/modules/layout_builder/src/Field/LayoutSectionItemListInterface.php b/core/modules/layout_builder/src/Field/LayoutSectionItemListInterface.php
new file mode 100644
index 0000000..0a604d7
--- /dev/null
+++ b/core/modules/layout_builder/src/Field/LayoutSectionItemListInterface.php
@@ -0,0 +1,41 @@
+<?php
+
+namespace Drupal\layout_builder\Field;
+
+use Drupal\Core\Field\FieldItemListInterface;
+
+/**
+ * Defines a item list class for layout section fields.
+ *
+ * @see \Drupal\layout_builder\Plugin\Field\FieldType\LayoutSectionItem
+ */
+interface LayoutSectionItemListInterface extends FieldItemListInterface {
+
+  /**
+   * {@inheritdoc}
+   *
+   * @return \Drupal\layout_builder\LayoutSectionItemInterface|null
+   *   The layout section item, if it exists.
+   */
+  public function get($index);
+
+  /**
+   * Adds a new item to the list.
+   *
+   * If an item exists at the given index, the item at that position and others
+   * after it are shifted backward.
+   *
+   * @param int $index
+   *   The position of the item in the list.
+   * @param mixed $value
+   *   The value of the item to be stored at the specified position.
+   *
+   * @return \Drupal\Core\TypedData\TypedDataInterface
+   *   The item that was appended.
+   *
+   * @todo Move to \Drupal\Core\TypedData\ListInterface directly in
+   *   https://www.drupal.org/node/2907417.
+   */
+  public function addItem($index, $value);
+
+}
diff --git a/core/modules/layout_builder/src/Form/ConfigureBlockForm.php b/core/modules/layout_builder/src/Form/ConfigureBlockForm.php
new file mode 100644
index 0000000..c004a32
--- /dev/null
+++ b/core/modules/layout_builder/src/Form/ConfigureBlockForm.php
@@ -0,0 +1,256 @@
+<?php
+
+namespace Drupal\layout_builder\Form;
+
+use Drupal\Component\Uuid\UuidInterface;
+use Drupal\Core\Block\BlockManagerInterface;
+use Drupal\Core\Block\BlockPluginInterface;
+use Drupal\Core\DependencyInjection\ClassResolverInterface;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Form\FormBase;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Form\SubformState;
+use Drupal\Core\Plugin\Context\ContextRepositoryInterface;
+use Drupal\Core\Plugin\ContextAwarePluginAssignmentTrait;
+use Drupal\Core\Plugin\ContextAwarePluginInterface;
+use Drupal\Core\Plugin\PluginFormFactoryInterface;
+use Drupal\Core\Plugin\PluginWithFormsInterface;
+use Drupal\layout_builder\Controller\LayoutRebuildTrait;
+use Drupal\layout_builder\LayoutTempstoreRepositoryInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Provides a form to configure a block.
+ */
+class ConfigureBlockForm extends FormBase {
+
+  use ContextAwarePluginAssignmentTrait;
+  use LayoutRebuildTrait;
+
+  /**
+   * The plugin being configured.
+   *
+   * @var \Drupal\Core\Block\BlockPluginInterface
+   */
+  protected $block;
+
+  /**
+   * The context repository.
+   *
+   * @var \Drupal\Core\Plugin\Context\ContextRepositoryInterface
+   */
+  protected $contextRepository;
+
+  /**
+   * The layout tempstore repository.
+   *
+   * @var \Drupal\layout_builder\LayoutTempstoreRepositoryInterface
+   */
+  protected $layoutTempstoreRepository;
+
+  /**
+   * The block manager.
+   *
+   * @var \Drupal\Core\Block\BlockManagerInterface
+   */
+  protected $blockManager;
+
+  /**
+   * The UUID generator.
+   *
+   * @var \Drupal\Component\Uuid\UuidInterface
+   */
+  protected $uuid;
+
+  /**
+   * The class resolver.
+   *
+   * @var \Drupal\Core\DependencyInjection\ClassResolver
+   */
+  protected $classResolver;
+
+  /**
+   * The plugin form manager.
+   *
+   * @var \Drupal\Core\Plugin\PluginFormFactoryInterface
+   */
+  protected $pluginFormFactory;
+
+  /**
+   * The field delta.
+   *
+   * @var int
+   */
+  protected $delta;
+
+  /**
+   * The current region.
+   *
+   * @var string
+   */
+  protected $region;
+
+  /**
+   * The entity.
+   *
+   * @var \Drupal\Core\Entity\EntityInterface
+   */
+  protected $entity;
+
+  /**
+   * Constructs a new ConfigureBlockForm.
+   *
+   * @param \Drupal\layout_builder\LayoutTempstoreRepositoryInterface $layout_tempstore_repository
+   *   The layout tempstore repository.
+   * @param \Drupal\Core\Plugin\Context\ContextRepositoryInterface $context_repository
+   *   The context repository.
+   * @param \Drupal\Core\Block\BlockManagerInterface $block_manager
+   *   The block manager.
+   * @param \Drupal\Component\Uuid\UuidInterface $uuid
+   *   The UUID generator.
+   * @param \Drupal\Core\DependencyInjection\ClassResolverInterface $class_resolver
+   *   The class resolver.
+   * @param \Drupal\Core\Plugin\PluginFormFactoryInterface $plugin_form_manager
+   *   The plugin form manager.
+   */
+  public function __construct(LayoutTempstoreRepositoryInterface $layout_tempstore_repository, ContextRepositoryInterface $context_repository, BlockManagerInterface $block_manager, UuidInterface $uuid, ClassResolverInterface $class_resolver, PluginFormFactoryInterface $plugin_form_manager) {
+    $this->layoutTempstoreRepository = $layout_tempstore_repository;
+    $this->contextRepository = $context_repository;
+    $this->blockManager = $block_manager;
+    $this->uuid = $uuid;
+    $this->classResolver = $class_resolver;
+    $this->pluginFormFactory = $plugin_form_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('layout_builder.tempstore_repository'),
+      $container->get('context.repository'),
+      $container->get('plugin.manager.block'),
+      $container->get('uuid'),
+      $container->get('class_resolver'),
+      $container->get('plugin_form.factory')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'layout_builder_configure_block';
+  }
+
+  /**
+   * Prepares the block plugin based on the block ID.
+   *
+   * @param string $block_id
+   *   Either a block ID, or the plugin ID used to create a new block.
+   * @param array $configuration
+   *   The block configuration.
+   *
+   * @return \Drupal\Core\Block\BlockPluginInterface
+   *   The block plugin.
+   */
+  protected function prepareBlock($block_id, array $configuration) {
+    if (!isset($configuration['uuid'])) {
+      $configuration['uuid'] = $this->uuid->generate();
+    }
+
+    return $this->blockManager->createInstance($block_id, $configuration);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state, EntityInterface $entity = NULL, $delta = NULL, $region = NULL, $plugin_id = NULL, $uuid = NULL) {
+    $this->entity = $entity;
+    $this->delta = $delta;
+    $this->region = $region;
+
+    $configuration = [];
+    if ($uuid) {
+      /** @var \Drupal\layout_builder\LayoutSectionItemInterface $field */
+      $field = $this->entity->layout_builder__layout->get($this->delta);
+      $plugin_id = $field->section[$region][$uuid]['block']['id'];
+      $configuration = $field->section[$region][$uuid]['block'];
+    }
+    $this->block = $this->prepareBlock($plugin_id, $configuration);
+
+    $form_state->setTemporaryValue('gathered_contexts', $this->contextRepository->getAvailableContexts());
+
+    // Some Block Plugins rely on the block_theme value to load theme settings.
+    // @see \Drupal\system\Plugin\Block\SystemBrandingBlock::blockForm().
+    $form_state->set('block_theme', $this->config('system.theme')->get('default'));
+
+    $form['#tree'] = TRUE;
+    $form['settings'] = [];
+    $subform_state = SubformState::createForSubform($form['settings'], $form, $form_state);
+    $form['settings'] = $this->getPluginForm($this->block)->buildConfigurationForm($form['settings'], $subform_state);
+
+    $form['actions']['submit'] = [
+      '#type' => 'submit',
+      '#value' => $uuid ? $this->t('Update') : $this->t('Add Block'),
+      '#button_type' => 'primary',
+      '#ajax' => [
+        'callback' => '::ajaxSubmit',
+      ],
+    ];
+
+    $form['#attached']['library'][] = 'core/drupal.dialog.ajax';
+
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateForm(array &$form, FormStateInterface $form_state) {
+    $sub_form_state = SubformState::createForSubform($form['settings'], $form, $form_state);
+    $this->getPluginForm($this->block)->validateConfigurationForm($form['settings'], $sub_form_state);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    // Call the plugin submit handler.
+    $sub_form_state = SubformState::createForSubform($form['settings'], $form, $form_state);
+    $this->getPluginForm($this->block)->submitConfigurationForm($form, $sub_form_state);
+
+    // If this block is context-aware, set the context mapping.
+    if ($this->block instanceof ContextAwarePluginInterface) {
+      $this->block->setContextMapping($sub_form_state->getValue('context_mapping', []));
+    }
+
+    $configuration = $this->block->getConfiguration();
+
+    /** @var \Drupal\layout_builder\LayoutSectionItemInterface $field */
+    $field = $this->entity->layout_builder__layout->get($this->delta);
+    $section = $field->section;
+    $section[$this->region][$configuration['uuid']]['block'] = $configuration;
+    $field->section = $section;
+
+    $this->layoutTempstoreRepository->set($this->entity);
+    $form_state->setRedirect("entity.{$this->entity->getEntityTypeId()}.layout", [$this->entity->getEntityTypeId() => $this->entity->id()]);
+  }
+
+  /**
+   * Retrieves the plugin form for a given block and operation.
+   *
+   * @param \Drupal\Core\Block\BlockPluginInterface $block
+   *   The block plugin.
+   *
+   * @return \Drupal\Core\Plugin\PluginFormInterface
+   *   The plugin form for the block.
+   */
+  protected function getPluginForm(BlockPluginInterface $block) {
+    if ($block instanceof PluginWithFormsInterface) {
+      return $this->pluginFormFactory->createInstance($block, 'configure');
+    }
+    return $block;
+  }
+
+}
diff --git a/core/modules/layout_builder/src/Form/ConfigureSectionForm.php b/core/modules/layout_builder/src/Form/ConfigureSectionForm.php
new file mode 100644
index 0000000..23bf9a7
--- /dev/null
+++ b/core/modules/layout_builder/src/Form/ConfigureSectionForm.php
@@ -0,0 +1,179 @@
+<?php
+
+namespace Drupal\layout_builder\Form;
+
+use Drupal\Core\DependencyInjection\ClassResolverInterface;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Form\FormBase;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Form\SubformState;
+use Drupal\Core\Layout\LayoutPluginManagerInterface;
+use Drupal\layout_builder\Controller\LayoutRebuildTrait;
+use Drupal\layout_builder\LayoutTempstoreRepositoryInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Provides a form for configuring a layout section.
+ */
+class ConfigureSectionForm extends FormBase {
+
+  use LayoutRebuildTrait;
+
+  /**
+   * The layout tempstore repository.
+   *
+   * @var \Drupal\layout_builder\LayoutTempstoreRepositoryInterface
+   */
+  protected $layoutTempstoreRepository;
+
+  /**
+   * The plugin being configured.
+   *
+   * @var \Drupal\Core\Layout\LayoutInterface|\Drupal\Core\Plugin\PluginFormInterface
+   */
+  protected $layout;
+
+  /**
+   * The class resolver.
+   *
+   * @var \Drupal\Core\DependencyInjection\ClassResolverInterface
+   */
+  protected $classResolver;
+
+  /**
+   * The layout manager.
+   *
+   * @var \Drupal\Core\Layout\LayoutPluginManagerInterface
+   */
+  protected $layoutManager;
+
+  /**
+   * The entity.
+   *
+   * @var \Drupal\Core\Entity\EntityInterface
+   */
+  protected $entity;
+
+  /**
+   * The field delta.
+   *
+   * @var int
+   */
+  protected $delta;
+
+  /**
+   * Indicates whether the section is being added or updated.
+   *
+   * @var bool
+   */
+  protected $isUpdate;
+
+  /**
+   * Constructs a new ConfigureSectionForm.
+   *
+   * @param \Drupal\layout_builder\LayoutTempstoreRepositoryInterface $layout_tempstore_repository
+   *   The layout tempstore repository.
+   * @param \Drupal\Core\Layout\LayoutPluginManagerInterface $layout_manager
+   *   The layout manager.
+   * @param \Drupal\Core\DependencyInjection\ClassResolverInterface $class_resolver
+   *   The class resolver.
+   */
+  public function __construct(LayoutTempstoreRepositoryInterface $layout_tempstore_repository, LayoutPluginManagerInterface $layout_manager, ClassResolverInterface $class_resolver) {
+    $this->layoutTempstoreRepository = $layout_tempstore_repository;
+    $this->layoutManager = $layout_manager;
+    $this->classResolver = $class_resolver;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('layout_builder.tempstore_repository'),
+      $container->get('plugin.manager.core.layout'),
+      $container->get('class_resolver')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'layout_builder_configure_section';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state, EntityInterface $entity = NULL, $delta = NULL, $plugin_id = NULL) {
+    $this->entity = $entity;
+    $this->delta = $delta;
+    $this->isUpdate = is_null($plugin_id);
+
+    $configuration = [];
+    if ($this->isUpdate) {
+      /** @var \Drupal\layout_builder\LayoutSectionItemInterface $field */
+      $field = $this->entity->layout_builder__layout->get($this->delta);
+      $plugin_id = $field->layout;
+      $configuration = $field->layout_settings;
+    }
+    $this->layout = $this->layoutManager->createInstance($plugin_id, $configuration);
+
+    $form['#tree'] = TRUE;
+    $form['layout_settings'] = [];
+    $subform_state = SubformState::createForSubform($form['layout_settings'], $form, $form_state);
+    $form['layout_settings'] = $this->layout->buildConfigurationForm($form['layout_settings'], $subform_state);
+
+    $form['actions']['submit'] = [
+      '#type' => 'submit',
+      '#value' => $this->isUpdate ? $this->t('Update') : $this->t('Add section'),
+      '#button_type' => 'primary',
+      '#ajax' => [
+        'callback' => '::ajaxSubmit',
+      ],
+    ];
+
+    $form['#attached']['library'][] = 'core/drupal.dialog.ajax';
+
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateForm(array &$form, FormStateInterface $form_state) {
+    $subform_state = SubformState::createForSubform($form['layout_settings'], $form, $form_state);
+    $this->layout->validateConfigurationForm($form['layout_settings'], $subform_state);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    // Call the plugin submit handler.
+    $subform_state = SubformState::createForSubform($form['layout_settings'], $form, $form_state);
+    $this->layout->submitConfigurationForm($form['layout_settings'], $subform_state);
+
+    $plugin_id = $this->layout->getPluginId();
+    $configuration = $this->layout->getConfiguration();
+
+    /** @var \Drupal\layout_builder\Field\LayoutSectionItemListInterface $field_list */
+    $field_list = $this->entity->layout_builder__layout;
+    if ($this->isUpdate) {
+      $field = $field_list->get($this->delta);
+      $field->layout = $plugin_id;
+      $field->layout_settings = $configuration;
+    }
+    else {
+      $field_list->addItem($this->delta, [
+        'layout' => $plugin_id,
+        'layout_settings' => $configuration,
+        'section' => [],
+      ]);
+    }
+
+    $this->layoutTempstoreRepository->set($this->entity);
+    $form_state->setRedirect("entity.{$this->entity->getEntityTypeId()}.layout", [$this->entity->getEntityTypeId() => $this->entity->id()]);
+  }
+
+}
diff --git a/core/modules/layout_builder/src/Form/LayoutRebuildConfirmFormBase.php b/core/modules/layout_builder/src/Form/LayoutRebuildConfirmFormBase.php
new file mode 100644
index 0000000..cbe0ea1
--- /dev/null
+++ b/core/modules/layout_builder/src/Form/LayoutRebuildConfirmFormBase.php
@@ -0,0 +1,104 @@
+<?php
+
+namespace Drupal\layout_builder\Form;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Form\ConfirmFormBase;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Url;
+use Drupal\layout_builder\Controller\LayoutRebuildTrait;
+use Drupal\layout_builder\LayoutTempstoreRepositoryInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Provides a base class for confirmation forms that rebuild the Layout Builder.
+ */
+abstract class LayoutRebuildConfirmFormBase extends ConfirmFormBase {
+
+  use LayoutRebuildTrait;
+
+  /**
+   * The layout tempstore repository.
+   *
+   * @var \Drupal\layout_builder\LayoutTempstoreRepositoryInterface
+   */
+  protected $layoutTempstoreRepository;
+
+  /**
+   * The entity.
+   *
+   * @var \Drupal\Core\Entity\EntityInterface
+   */
+  protected $entity;
+
+  /**
+   * The field delta.
+   *
+   * @var int
+   */
+  protected $delta;
+
+  /**
+   * Constructs a new RemoveSectionForm.
+   *
+   * @param \Drupal\layout_builder\LayoutTempstoreRepositoryInterface $layout_tempstore_repository
+   *   The layout tempstore repository.
+   */
+  public function __construct(LayoutTempstoreRepositoryInterface $layout_tempstore_repository) {
+    $this->layoutTempstoreRepository = $layout_tempstore_repository;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('layout_builder.tempstore_repository')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCancelUrl() {
+    return Url::fromRoute("entity.{$this->entity->getEntityTypeId()}.layout", [$this->entity->getEntityTypeId() => $this->entity->id()]);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state, EntityInterface $entity = NULL, $delta = NULL) {
+    $this->entity = $entity;
+    $this->delta = $delta;
+
+    $form = parent::buildForm($form, $form_state);
+
+    $form['#attached']['library'][] = 'core/drupal.dialog.ajax';
+    $form['actions']['submit']['#ajax']['callback'] = '::ajaxSubmit';
+
+    $form['actions']['cancel']['#attributes']['class'][] = 'dialog-cancel';
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    $this->handleEntity($this->entity, $form_state);
+
+    $this->layoutTempstoreRepository->set($this->entity);
+
+    $form_state->setRedirectUrl($this->getCancelUrl());
+  }
+
+  /**
+   * Performs any actions on the layout entity before saving.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The entity.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   */
+  abstract protected function handleEntity(EntityInterface $entity, FormStateInterface $form_state);
+
+}
diff --git a/core/modules/layout_builder/src/Form/RemoveBlockForm.php b/core/modules/layout_builder/src/Form/RemoveBlockForm.php
new file mode 100644
index 0000000..4254472
--- /dev/null
+++ b/core/modules/layout_builder/src/Form/RemoveBlockForm.php
@@ -0,0 +1,68 @@
+<?php
+
+namespace Drupal\layout_builder\Form;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Provides a form to confirm the removal of a block.
+ */
+class RemoveBlockForm extends LayoutRebuildConfirmFormBase {
+
+  /**
+   * The current region.
+   *
+   * @var string
+   */
+  protected $region;
+
+  /**
+   * The UUID of the block being removed.
+   *
+   * @var string
+   */
+  protected $uuid;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getQuestion() {
+    return $this->t('Are you sure you want to remove this block?');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getConfirmText() {
+    return $this->t('Remove');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'layout_builder_remove_block';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state, EntityInterface $entity = NULL, $delta = NULL, $region = NULL, $uuid = NULL) {
+    $this->region = $region;
+    $this->uuid = $uuid;
+    return parent::buildForm($form, $form_state, $entity, $delta);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function handleEntity(EntityInterface $entity, FormStateInterface $form_state) {
+    /** @var \Drupal\layout_builder\LayoutSectionItemInterface $field */
+    $field = $entity->layout_builder__layout->get($this->delta);
+    $values = $field->section;
+    unset($values[$this->region][$this->uuid]);
+    $field->section = $values;
+  }
+
+}
diff --git a/core/modules/layout_builder/src/Form/RemoveSectionForm.php b/core/modules/layout_builder/src/Form/RemoveSectionForm.php
new file mode 100644
index 0000000..7b2f564
--- /dev/null
+++ b/core/modules/layout_builder/src/Form/RemoveSectionForm.php
@@ -0,0 +1,41 @@
+<?php
+
+namespace Drupal\layout_builder\Form;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Provides a form to confirm the removal of a section.
+ */
+class RemoveSectionForm extends LayoutRebuildConfirmFormBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'layout_builder_remove_section';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getQuestion() {
+    return $this->t('Are you sure you want to remove this section?');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getConfirmText() {
+    return $this->t('Remove');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function handleEntity(EntityInterface $entity, FormStateInterface $form_state) {
+    $entity->layout_builder__layout->removeItem($this->delta);
+  }
+
+}
diff --git a/core/modules/layout_builder/src/LayoutSectionBuilder.php b/core/modules/layout_builder/src/LayoutSectionBuilder.php
new file mode 100644
index 0000000..fdd6b36
--- /dev/null
+++ b/core/modules/layout_builder/src/LayoutSectionBuilder.php
@@ -0,0 +1,199 @@
+<?php
+
+namespace Drupal\layout_builder;
+
+use Drupal\Component\Plugin\Exception\PluginException;
+use Drupal\Core\Block\BlockManagerInterface;
+use Drupal\Core\Cache\CacheableMetadata;
+use Drupal\Core\Layout\LayoutInterface;
+use Drupal\Core\Layout\LayoutPluginManagerInterface;
+use Drupal\Core\Plugin\Context\ContextHandlerInterface;
+use Drupal\Core\Plugin\Context\ContextRepositoryInterface;
+use Drupal\Core\Plugin\ContextAwarePluginInterface;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+
+/**
+ * Builds the UI for layout sections.
+ */
+class LayoutSectionBuilder {
+
+  use StringTranslationTrait;
+
+  /**
+   * The current user.
+   *
+   * @var \Drupal\Core\Session\AccountInterface
+   */
+  protected $account;
+
+  /**
+   * The layout plugin manager.
+   *
+   * @var \Drupal\Core\Layout\LayoutPluginManagerInterface
+   */
+  protected $layoutPluginManager;
+
+  /**
+   * The block plugin manager.
+   *
+   * @var \Drupal\Core\Block\BlockManagerInterface
+   */
+  protected $blockManager;
+
+  /**
+   * The plugin context handler.
+   *
+   * @var \Drupal\Core\Plugin\Context\ContextHandlerInterface
+   */
+  protected $contextHandler;
+
+  /**
+   * The context manager service.
+   *
+   * @var \Drupal\Core\Plugin\Context\ContextRepositoryInterface
+   */
+  protected $contextRepository;
+
+  /**
+   * Constructs a LayoutSectionFormatter object.
+   *
+   * @param \Drupal\Core\Session\AccountInterface $account
+   *   The current user.
+   * @param \Drupal\Core\Layout\LayoutPluginManagerInterface $layoutPluginManager
+   *   The layout plugin manager.
+   * @param \Drupal\Core\Block\BlockManagerInterface $blockManager
+   *   THe block plugin manager.
+   * @param \Drupal\Core\Plugin\Context\ContextHandlerInterface $context_handler
+   *   The ContextHandler for applying contexts to conditions properly.
+   * @param \Drupal\Core\Plugin\Context\ContextRepositoryInterface $context_repository
+   *   The lazy context repository service.
+   */
+  public function __construct(AccountInterface $account, LayoutPluginManagerInterface $layoutPluginManager, BlockManagerInterface $blockManager, ContextHandlerInterface $context_handler, ContextRepositoryInterface $context_repository) {
+    $this->account = $account;
+    $this->layoutPluginManager = $layoutPluginManager;
+    $this->blockManager = $blockManager;
+    $this->contextHandler = $context_handler;
+    $this->contextRepository = $context_repository;
+  }
+
+  /**
+   * Builds the render array for the layout section.
+   *
+   * @param \Drupal\Core\Layout\LayoutInterface $layout
+   *   The ID of the layout.
+   * @param array $section
+   *   An array of configuration, keyed first by region and then by block UUID.
+   *
+   * @return array
+   *   The render array for a given section.
+   */
+  public function buildSectionFromLayout(LayoutInterface $layout, array $section) {
+    $cacheability = CacheableMetadata::createFromRenderArray([]);
+
+    $regions = [];
+    $weight = 0;
+    foreach ($section as $region => $blocks) {
+      if (!is_array($blocks)) {
+        throw new \InvalidArgumentException(sprintf('The "%s" region in the "%s" layout has invalid configuration', $region, $layout->getPluginId()));
+      }
+
+      foreach ($blocks as $uuid => $configuration) {
+        if (!is_array($configuration) || !isset($configuration['block'])) {
+          throw new \InvalidArgumentException(sprintf('The block with UUID of "%s" has invalid configuration', $uuid));
+        }
+
+        if ($block_output = $this->buildBlock($uuid, $configuration['block'], $cacheability)) {
+          $block_output['#weight'] = $weight++;
+          $regions[$region][$uuid] = $block_output;
+        }
+      }
+    }
+
+    $result = $layout->build($regions);
+    $cacheability->applyTo($result);
+    return $result;
+  }
+
+  /**
+   * Builds the render array for the layout section.
+   *
+   * @param string $layout_id
+   *   The ID of the layout.
+   * @param array $layout_settings
+   *   The configuration for the layout.
+   * @param array $section
+   *   An array of configuration, keyed first by region and then by block UUID.
+   *
+   * @return array
+   *   The render array for a given section.
+   */
+  public function buildSection($layout_id, array $layout_settings, array $section) {
+    $layout = $this->layoutPluginManager->createInstance($layout_id, $layout_settings);
+    return $this->buildSectionFromLayout($layout, $section);
+  }
+
+  /**
+   * Builds the render array for a given block.
+   *
+   * @param string $uuid
+   *   The UUID of this block instance.
+   * @param array $configuration
+   *   An array of configuration relevant to the block instance. Must contain
+   *   the plugin ID with the key 'id'.
+   * @param \Drupal\Core\Cache\CacheableMetadata $cacheability
+   *   The cacheability metadata.
+   *
+   * @return array|null
+   *   The render array representing this block, if accessible. NULL otherwise.
+   */
+  protected function buildBlock($uuid, array $configuration, CacheableMetadata $cacheability) {
+    $block = $this->getBlock($uuid, $configuration);
+
+    $access = $block->access($this->account, TRUE);
+    $cacheability->addCacheableDependency($access);
+
+    $block_output = NULL;
+    if ($access->isAllowed()) {
+      $block_output = [
+        '#theme' => 'block',
+        '#configuration' => $block->getConfiguration(),
+        '#plugin_id' => $block->getPluginId(),
+        '#base_plugin_id' => $block->getBaseId(),
+        '#derivative_plugin_id' => $block->getDerivativeId(),
+        'content' => $block->build(),
+      ];
+      $cacheability->addCacheableDependency($block);
+    }
+    return $block_output;
+  }
+
+  /**
+   * Gets a block instance.
+   *
+   * @param string $uuid
+   *   The UUID of this block instance.
+   * @param array $configuration
+   *   An array of configuration relevant to the block instance. Must contain
+   *   the plugin ID with the key 'id'.
+   *
+   * @return \Drupal\Core\Block\BlockPluginInterface
+   *   The block instance.
+   *
+   * @throws \Drupal\Component\Plugin\Exception\PluginException
+   *   Thrown when the configuration parameter does not contain 'id'.
+   */
+  protected function getBlock($uuid, array $configuration) {
+    if (!isset($configuration['id'])) {
+      throw new PluginException(sprintf('No plugin ID specified for block with "%s" UUID', $uuid));
+    }
+
+    $block = $this->blockManager->createInstance($configuration['id'], $configuration);
+    if ($block instanceof ContextAwarePluginInterface) {
+      $contexts = $this->contextRepository->getRuntimeContexts(array_values($block->getContextMapping()));
+      $this->contextHandler->applyContextMapping($block, $contexts);
+    }
+    return $block;
+  }
+
+}
diff --git a/core/modules/layout_builder/src/LayoutSectionItemInterface.php b/core/modules/layout_builder/src/LayoutSectionItemInterface.php
new file mode 100644
index 0000000..48e081a
--- /dev/null
+++ b/core/modules/layout_builder/src/LayoutSectionItemInterface.php
@@ -0,0 +1,16 @@
+<?php
+
+namespace Drupal\layout_builder;
+
+use Drupal\Core\Field\FieldItemInterface;
+
+/**
+ * Defines an interface for the layout section field item.
+ *
+ * @property string layout
+ * @property array[] layout_settings
+ * @property array[] section
+ */
+interface LayoutSectionItemInterface extends FieldItemInterface {
+
+}
diff --git a/core/modules/layout_builder/src/LayoutTempstoreRepository.php b/core/modules/layout_builder/src/LayoutTempstoreRepository.php
new file mode 100644
index 0000000..af6f2d1
--- /dev/null
+++ b/core/modules/layout_builder/src/LayoutTempstoreRepository.php
@@ -0,0 +1,98 @@
+<?php
+
+namespace Drupal\layout_builder;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Entity\RevisionableInterface;
+use Drupal\user\SharedTempStoreFactory;
+
+/**
+ * Provides a mechanism for loading layouts from tempstore.
+ */
+class LayoutTempstoreRepository implements LayoutTempstoreRepositoryInterface {
+
+  /**
+   * The shared tempstore factory.
+   *
+   * @var \Drupal\user\SharedTempStoreFactory
+   */
+  protected $tempStoreFactory;
+
+  /**
+   * The entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * LayoutTempstoreRepository constructor.
+   *
+   * @param \Drupal\user\SharedTempStoreFactory $temp_store_factory
+   *   The shared tempstore factory.
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The entity type manager.
+   */
+  public function __construct(SharedTempStoreFactory $temp_store_factory, EntityTypeManagerInterface $entity_type_manager) {
+    $this->tempStoreFactory = $temp_store_factory;
+    $this->entityTypeManager = $entity_type_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function get(EntityInterface $entity) {
+    list($collection, $id) = $this->generateTempstoreId($entity);
+    $tempstore = $this->tempStoreFactory->get($collection)->get($id);
+    if (!empty($tempstore['entity'])) {
+      return $tempstore['entity'];
+    }
+    return $entity;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFromId($entity_type_id, $entity_id) {
+    $entity = $this->entityTypeManager->getStorage($entity_type_id)->loadRevision($entity_id);
+    return $this->get($entity);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function set(EntityInterface $entity) {
+    list($collection, $id) = $this->generateTempstoreId($entity);
+    $this->tempStoreFactory->get($collection)->set($id, ['entity' => $entity]);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function delete(EntityInterface $entity) {
+    if ($this->get($entity)) {
+      list($collection, $id) = $this->generateTempstoreId($entity);
+      $this->tempStoreFactory->get($collection)->delete($id);
+    }
+  }
+
+  /**
+   * Generates a collection and ID for putting an entity in tempstore.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The entity being stored.
+   *
+   * @return array
+   *   An array containing the collection name and the tempstore ID.
+   */
+  protected function generateTempstoreId(EntityInterface $entity) {
+    $collection = $entity->getEntityTypeId() . '.layout_builder__layout';
+    $id = "{$entity->id()}.{$entity->language()->getId()}";
+    if ($entity instanceof RevisionableInterface) {
+      $id .= '.' . $entity->getRevisionId();
+    }
+    return [$collection, $id];
+  }
+
+}
diff --git a/core/modules/layout_builder/src/LayoutTempstoreRepositoryInterface.php b/core/modules/layout_builder/src/LayoutTempstoreRepositoryInterface.php
new file mode 100644
index 0000000..8043c84
--- /dev/null
+++ b/core/modules/layout_builder/src/LayoutTempstoreRepositoryInterface.php
@@ -0,0 +1,54 @@
+<?php
+
+namespace Drupal\layout_builder;
+
+use Drupal\Core\Entity\EntityInterface;
+
+/**
+ * Provides an interface for loading layouts from tempstore.
+ */
+interface LayoutTempstoreRepositoryInterface {
+
+  /**
+   * Gets the tempstore version of an entity, if it exists.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The entity to check for in tempstore.
+   *
+   * @return \Drupal\Core\Entity\EntityInterface
+   *   Either the version of this entity from tempstore, or the passed entity if
+   *   none exists.
+   */
+  public function get(EntityInterface $entity);
+
+  /**
+   * Loads an entity from tempstore given the entity ID.
+   *
+   * @param string $entity_type_id
+   *   The entity type ID.
+   * @param string $entity_id
+   *   The entity ID (or revision ID).
+   *
+   * @return \Drupal\Core\Entity\EntityInterface
+   *   Either the version of this entity from tempstore, or the entity from
+   *   storage if none exists.
+   */
+  public function getFromId($entity_type_id, $entity_id);
+
+  /**
+   * Stores this entity in tempstore.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The entity to set in tempstore.
+   */
+  public function set(EntityInterface $entity);
+
+  /**
+   * Removes the tempstore version of an entity.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The entity to remove from tempstore.
+   */
+  public function delete(EntityInterface $entity);
+
+}
diff --git a/core/modules/layout_builder/src/Plugin/Derivative/LayoutBuilderLocalTaskDeriver.php b/core/modules/layout_builder/src/Plugin/Derivative/LayoutBuilderLocalTaskDeriver.php
new file mode 100644
index 0000000..9ef0db4
--- /dev/null
+++ b/core/modules/layout_builder/src/Plugin/Derivative/LayoutBuilderLocalTaskDeriver.php
@@ -0,0 +1,92 @@
+<?php
+
+namespace Drupal\layout_builder\Plugin\Derivative;
+
+use Drupal\Component\Plugin\Derivative\DeriverBase;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Entity\FieldableEntityInterface;
+use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\layout_builder\Plugin\Menu\LayoutBuilderLocalTask;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Provides local task definitions for the layout builder user interface.
+ */
+class LayoutBuilderLocalTaskDeriver extends DeriverBase implements ContainerDeriverInterface {
+
+  use StringTranslationTrait;
+
+  /**
+   * The entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * Constructs a new LayoutBuilderLocalTaskDeriver.
+   *
+   * @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, $base_plugin_id) {
+    return new static(
+      $container->get('entity_type.manager')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDerivativeDefinitions($base_plugin_definition) {
+    foreach (array_keys($this->getEntityTypes()) as $entity_type_id) {
+      $this->derivatives["entity.$entity_type_id.layout"] = $base_plugin_definition + [
+        'route_name' => "entity.$entity_type_id.layout",
+        'weight' => 15,
+        'title' => $this->t('Layout'),
+        'base_route' => "entity.$entity_type_id.canonical",
+        'entity_type_id' => $entity_type_id,
+        'class' => LayoutBuilderLocalTask::class,
+      ];
+      $this->derivatives["entity.$entity_type_id.save_layout"] = $base_plugin_definition + [
+        'route_name' => "entity.$entity_type_id.save_layout",
+        'title' => $this->t('Save Layout'),
+        'parent_id' => "layout_builder_ui:entity.$entity_type_id.layout",
+        'entity_type_id' => $entity_type_id,
+        'class' => LayoutBuilderLocalTask::class,
+      ];
+      $this->derivatives["entity.$entity_type_id.cancel_layout"] = $base_plugin_definition + [
+        'route_name' => "entity.$entity_type_id.cancel_layout",
+        'title' => $this->t('Cancel Layout'),
+        'parent_id' => "layout_builder_ui:entity.$entity_type_id.layout",
+        'entity_type_id' => $entity_type_id,
+        'class' => LayoutBuilderLocalTask::class,
+        'weight' => 5,
+      ];
+    }
+
+    return $this->derivatives;
+  }
+
+  /**
+   * Returns an array of relevant entity types.
+   *
+   * @return \Drupal\Core\Entity\EntityTypeInterface[]
+   *   An array of entity types.
+   */
+  protected function getEntityTypes() {
+    return array_filter($this->entityTypeManager->getDefinitions(), function (EntityTypeInterface $entity_type) {
+      return $entity_type->entityClassImplements(FieldableEntityInterface::class) && $entity_type->hasLinkTemplate('canonical') && $entity_type->hasViewBuilderClass();
+    });
+  }
+
+}
diff --git a/core/modules/layout_builder/src/Plugin/Field/FieldFormatter/LayoutSectionFormatter.php b/core/modules/layout_builder/src/Plugin/Field/FieldFormatter/LayoutSectionFormatter.php
new file mode 100644
index 0000000..bbc8b2f
--- /dev/null
+++ b/core/modules/layout_builder/src/Plugin/Field/FieldFormatter/LayoutSectionFormatter.php
@@ -0,0 +1,87 @@
+<?php
+
+namespace Drupal\layout_builder\Plugin\Field\FieldFormatter;
+
+use Drupal\Core\Field\FieldDefinitionInterface;
+use Drupal\Core\Field\FieldItemListInterface;
+use Drupal\Core\Field\FormatterBase;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\layout_builder\LayoutSectionBuilder;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Plugin implementation of the 'layout_section' formatter.
+ *
+ * @FieldFormatter(
+ *   id = "layout_section",
+ *   label = @Translation("Layout Section"),
+ *   field_types = {
+ *     "layout_section"
+ *   }
+ * )
+ */
+class LayoutSectionFormatter extends FormatterBase implements ContainerFactoryPluginInterface {
+
+  /**
+   * The layout section builder.
+   *
+   * @var \Drupal\layout_builder\LayoutSectionBuilder
+   */
+  protected $builder;
+
+  /**
+   * Constructs a LayoutSectionFormatter object.
+   *
+   * @param \Drupal\layout_builder\LayoutSectionBuilder $builder
+   *   The layout section builder.
+   * @param string $plugin_id
+   *   The plugin ID for the formatter.
+   * @param mixed $plugin_definition
+   *   The plugin implementation definition.
+   * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
+   *   The definition of the field to which the formatter is associated.
+   * @param array $settings
+   *   The formatter settings.
+   * @param string $label
+   *   The formatter label display setting.
+   * @param string $view_mode
+   *   The view mode.
+   * @param array $third_party_settings
+   *   Any third party settings.
+   */
+  public function __construct(LayoutSectionBuilder $builder, $plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, $label, $view_mode, array $third_party_settings) {
+    $this->builder = $builder;
+    parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $label, $view_mode, $third_party_settings);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $container->get('layout_builder.builder'),
+      $plugin_id,
+      $plugin_definition,
+      $configuration['field_definition'],
+      $configuration['settings'],
+      $configuration['label'],
+      $configuration['view_mode'],
+      $configuration['third_party_settings']
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function viewElements(FieldItemListInterface $items, $langcode) {
+    $elements = [];
+
+    /** @var \Drupal\layout_builder\LayoutSectionItemInterface[] $items */
+    foreach ($items as $delta => $item) {
+      $elements[$delta] = $this->builder->buildSection($item->layout, $item->layout_settings, $item->section);
+    }
+
+    return $elements;
+  }
+
+}
diff --git a/core/modules/layout_builder/src/Plugin/Field/FieldType/LayoutSectionItem.php b/core/modules/layout_builder/src/Plugin/Field/FieldType/LayoutSectionItem.php
new file mode 100644
index 0000000..d564ce0
--- /dev/null
+++ b/core/modules/layout_builder/src/Plugin/Field/FieldType/LayoutSectionItem.php
@@ -0,0 +1,110 @@
+<?php
+
+namespace Drupal\layout_builder\Plugin\Field\FieldType;
+
+use Drupal\Core\Field\FieldDefinitionInterface;
+use Drupal\Core\Field\FieldItemBase;
+use Drupal\Core\Field\FieldStorageDefinitionInterface;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Drupal\Core\TypedData\DataDefinition;
+use Drupal\Core\TypedData\MapDataDefinition;
+use Drupal\layout_builder\LayoutSectionItemInterface;
+
+/**
+ * Plugin implementation of the 'layout_section' field type.
+ *
+ * @FieldType(
+ *   id = "layout_section",
+ *   label = @Translation("Layout Section"),
+ *   description = @Translation("Layout Section"),
+ *   default_formatter = "layout_section",
+ *   list_class = "\Drupal\layout_builder\Field\LayoutSectionItemList",
+ *   no_ui = TRUE,
+ *   cardinality = \Drupal\Core\Field\FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED
+ * )
+ */
+class LayoutSectionItem extends FieldItemBase implements LayoutSectionItemInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
+    // Prevent early t() calls by using the TranslatableMarkup.
+    $properties['layout'] = DataDefinition::create('string')
+      ->setLabel(new TranslatableMarkup('Layout'))
+      ->setSetting('case_sensitive', FALSE)
+      ->setRequired(TRUE);
+    $properties['layout_settings'] = MapDataDefinition::create('map')
+      ->setLabel(new TranslatableMarkup('Layout Settings'))
+      ->setRequired(FALSE);
+    $properties['section'] = MapDataDefinition::create('map')
+      ->setLabel(new TranslatableMarkup('Layout Section'))
+      ->setRequired(FALSE);
+
+    return $properties;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __get($name) {
+    // @todo parent::__get() does not return default values unless
+    //   $this->properties has been initialized. Remove in
+    //   https://www.drupal.org/node/2905922.
+    $this->getProperties();
+
+    return parent::__get($name);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function mainPropertyName() {
+    return 'section';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function schema(FieldStorageDefinitionInterface $field_definition) {
+    $schema = [
+      'columns' => [
+        'layout' => [
+          'type' => 'varchar',
+          'length' => '255',
+          'binary' => FALSE,
+        ],
+        'layout_settings' => [
+          'type' => 'blob',
+          'size' => 'normal',
+          'serialize' => TRUE,
+        ],
+        'section' => [
+          'type' => 'blob',
+          'size' => 'normal',
+          'serialize' => TRUE,
+        ],
+      ],
+    ];
+
+    return $schema;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function generateSampleValue(FieldDefinitionInterface $field_definition) {
+    $values['layout'] = 'layout_onecol';
+    $values['layout_settings'] = [];
+    $values['section'] = [];
+    return $values;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isEmpty() {
+    return empty($this->layout);
+  }
+
+}
diff --git a/core/modules/layout_builder/src/Plugin/Menu/LayoutBuilderLocalTask.php b/core/modules/layout_builder/src/Plugin/Menu/LayoutBuilderLocalTask.php
new file mode 100644
index 0000000..329f5d3
--- /dev/null
+++ b/core/modules/layout_builder/src/Plugin/Menu/LayoutBuilderLocalTask.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace Drupal\layout_builder\Plugin\Menu;
+
+use Drupal\Core\Menu\LocalTaskDefault;
+use Drupal\Core\Routing\RouteMatchInterface;
+
+/**
+ * Provides route parameters needed to link to layout related tabs.
+ */
+class LayoutBuilderLocalTask extends LocalTaskDefault {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getRouteParameters(RouteMatchInterface $route_match) {
+    $parameters = parent::getRouteParameters($route_match);
+
+    $parameters['entity'] = $route_match->getParameter('entity');
+    return $parameters;
+  }
+
+}
diff --git a/core/modules/layout_builder/src/Routing/LayoutBuilderRouteEnhancer.php b/core/modules/layout_builder/src/Routing/LayoutBuilderRouteEnhancer.php
new file mode 100644
index 0000000..8aee689
--- /dev/null
+++ b/core/modules/layout_builder/src/Routing/LayoutBuilderRouteEnhancer.php
@@ -0,0 +1,31 @@
+<?php
+
+namespace Drupal\layout_builder\Routing;
+
+use Drupal\Core\Routing\Enhancer\RouteEnhancerInterface;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Routing\Route;
+
+/**
+ * Enhances routes to ensure the entity is available with a generic name.
+ */
+class LayoutBuilderRouteEnhancer implements RouteEnhancerInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function applies(Route $route) {
+    // Find layout builder routes that override existing paths.
+    return $route->hasOption('_layout_builder');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function enhance(array $defaults, Request $request) {
+    // Copy the entity by reference so that any changes are reflected.
+    $defaults['entity'] = &$defaults[$defaults['entity_type_id']];
+    return $defaults;
+  }
+
+}
diff --git a/core/modules/layout_builder/src/Routing/LayoutBuilderRoutes.php b/core/modules/layout_builder/src/Routing/LayoutBuilderRoutes.php
new file mode 100644
index 0000000..97adac6
--- /dev/null
+++ b/core/modules/layout_builder/src/Routing/LayoutBuilderRoutes.php
@@ -0,0 +1,148 @@
+<?php
+
+namespace Drupal\layout_builder\Routing;
+
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Entity\FieldableEntityInterface;
+use Drupal\Core\Routing\RouteSubscriberBase;
+use Symfony\Component\Routing\Route;
+use Symfony\Component\Routing\RouteCollection;
+
+/**
+ * Provides routes for the Layout Builder UI.
+ */
+class LayoutBuilderRoutes extends RouteSubscriberBase {
+
+  /**
+   * The entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * Constructs a new LayoutBuilderRoutes.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The entity type manager.
+   */
+  public function __construct(EntityTypeManagerInterface $entity_type_manager) {
+    $this->entityTypeManager = $entity_type_manager;
+  }
+
+  /**
+   * Generates layout builder routes.
+   *
+   * @return \Symfony\Component\Routing\Route[]
+   *   An array of route objects.
+   */
+  public function getRoutes() {
+    $routes = [];
+
+    foreach ($this->getEntityTypes() as $entity_type_id => $entity_type) {
+      $template = $entity_type->getLinkTemplate('canonical');
+      $route = (new Route("$template/layout"))
+        ->setDefaults([
+          '_controller' => '\Drupal\layout_builder\Controller\LayoutBuilderController::layout',
+          '_title_callback' => '\Drupal\layout_builder\Controller\LayoutBuilderController::title',
+          'entity' => NULL,
+          'entity_type_id' => $entity_type_id,
+        ])
+        ->addRequirements([
+          $entity_type_id => '\d+',
+          '_has_layout_section' => 'true',
+        ])
+        ->addOptions([
+          '_layout_builder' => TRUE,
+          'parameters' => [
+            $entity_type_id => [
+              'type' => 'entity:{entity_type_id}',
+              'layout_builder_tempstore' => TRUE,
+            ],
+          ],
+        ]);
+      $routes["entity.$entity_type_id.layout"] = $route;
+
+      $route = (new Route("$template/layout/save"))
+        ->setDefaults([
+          '_controller' => '\Drupal\layout_builder\Controller\LayoutBuilderController::saveLayout',
+          'entity' => NULL,
+          'entity_type_id' => $entity_type_id,
+        ])
+        ->addRequirements([
+          $entity_type_id => '\d+',
+          '_has_layout_section' => 'true',
+        ])
+        ->addOptions([
+          '_layout_builder' => TRUE,
+          'parameters' => [
+            $entity_type_id => [
+              'type' => 'entity:{entity_type_id}',
+              'layout_builder_tempstore' => TRUE,
+            ],
+          ],
+        ]);
+      $routes["entity.$entity_type_id.save_layout"] = $route;
+
+      $route = (new Route("$template/layout/cancel"))
+        ->setDefaults([
+          '_controller' => '\Drupal\layout_builder\Controller\LayoutBuilderController::cancelLayout',
+          'entity' => NULL,
+          'entity_type_id' => $entity_type_id,
+        ])
+        ->addRequirements([
+          $entity_type_id => '\d+',
+          '_has_layout_section' => 'true',
+        ])
+        ->addOptions([
+          '_layout_builder' => TRUE,
+          'parameters' => [
+            $entity_type_id => [
+              'type' => 'entity:{entity_type_id}',
+              'layout_builder_tempstore' => TRUE,
+            ],
+          ],
+        ]);
+      $routes["entity.$entity_type_id.cancel_layout"] = $route;
+    }
+    return $routes;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function alterRoutes(RouteCollection $collection) {
+    $templates = ['canonical', 'edit_form', 'delete_form'];
+    foreach ($this->getEntityTypes() as $entity_type) {
+      foreach ($templates as $template) {
+        // Mark this as a Layout Builder route so that links like local tasks
+        // will be enhanced.
+        if ($route = $collection->get('entity.' . $entity_type->id() . '.' . $template)) {
+          $parameters = $route->getOption('parameters');
+          $parameters[$entity_type->id()]['type'] = 'entity:{entity_type_id}';
+          $parameters[$entity_type->id()]['layout_builder_tempstore'] = TRUE;
+          $route->setOption('parameters', $parameters);
+          $route->setOption('_layout_builder', TRUE);
+          $route->addDefaults([
+            'entity' => NULL,
+            'entity_type_id' => $entity_type->id(),
+          ]);
+        }
+      }
+    }
+  }
+
+  /**
+   * Returns an array of relevant entity types.
+   *
+   * @return \Drupal\Core\Entity\EntityTypeInterface[]
+   *   An array of entity types.
+   */
+  protected function getEntityTypes() {
+    return array_filter($this->entityTypeManager->getDefinitions(), function (EntityTypeInterface $entity_type) {
+      return $entity_type->entityClassImplements(FieldableEntityInterface::class) && $entity_type->hasLinkTemplate('canonical') && $entity_type->hasViewBuilderClass();
+    });
+  }
+
+}
diff --git a/core/modules/layout_builder/src/Routing/LayoutTempstoreParamConverter.php b/core/modules/layout_builder/src/Routing/LayoutTempstoreParamConverter.php
new file mode 100644
index 0000000..be55f34
--- /dev/null
+++ b/core/modules/layout_builder/src/Routing/LayoutTempstoreParamConverter.php
@@ -0,0 +1,52 @@
+<?php
+
+namespace Drupal\layout_builder\Routing;
+
+use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\ParamConverter\EntityConverter;
+use Drupal\Core\ParamConverter\ParamConverterInterface;
+use Drupal\layout_builder\LayoutTempstoreRepositoryInterface;
+use Symfony\Component\Routing\Route;
+
+/**
+ * Loads the entity from the layout tempstore.
+ */
+class LayoutTempstoreParamConverter extends EntityConverter implements ParamConverterInterface {
+
+  /**
+   * The layout tempstore repository.
+   *
+   * @var \Drupal\layout_builder\LayoutTempstoreRepositoryInterface
+   */
+  protected $layoutTempstoreRepository;
+
+  /**
+   * Constructs a new LayoutTempstoreParamConverter.
+   *
+   * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
+   *   The entity manager.
+   * @param \Drupal\layout_builder\LayoutTempstoreRepositoryInterface $layout_tempstore_repository
+   *   The layout tempstore repository.
+   */
+  public function __construct(EntityManagerInterface $entity_manager, LayoutTempstoreRepositoryInterface $layout_tempstore_repository) {
+    parent::__construct($entity_manager);
+    $this->layoutTempstoreRepository = $layout_tempstore_repository;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function convert($value, $definition, $name, array $defaults) {
+    if ($entity = parent::convert($value, $definition, $name, $defaults)) {
+      return $this->layoutTempstoreRepository->get($entity);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function applies($definition, $name, Route $route) {
+    return !empty($definition['layout_builder_tempstore']);
+  }
+
+}
diff --git a/core/modules/layout_builder/tests/src/Functional/LayoutSectionTest.php b/core/modules/layout_builder/tests/src/Functional/LayoutSectionTest.php
new file mode 100644
index 0000000..0e30d14
--- /dev/null
+++ b/core/modules/layout_builder/tests/src/Functional/LayoutSectionTest.php
@@ -0,0 +1,351 @@
+<?php
+
+namespace Drupal\Tests\layout_builder\Functional;
+
+use Drupal\Core\Entity\Entity\EntityViewDisplay;
+use Drupal\language\Entity\ConfigurableLanguage;
+use Drupal\Tests\BrowserTestBase;
+
+/**
+ * Tests the rendering of a layout section field.
+ *
+ * @group layout_builder
+ */
+class LayoutSectionTest extends BrowserTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['layout_builder', 'node', 'block_test'];
+
+  /**
+   * The name of the layout section field.
+   *
+   * @var string
+   */
+  protected $fieldName = 'layout_builder__layout';
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->createContentType([
+      'type' => 'bundle_with_section_field',
+    ]);
+    $this->createContentType([
+      'type' => 'bundle_without_section_field',
+    ]);
+
+    layout_builder_add_layout_section_field('node', 'bundle_with_section_field');
+    $display = EntityViewDisplay::load('node.bundle_with_section_field.default');
+    $display->setThirdPartySetting('layout_builder', 'allow_custom', TRUE);
+    $display->save();
+
+    $this->drupalLogin($this->drupalCreateUser([
+      'configure any layout',
+    ], 'foobar'));
+  }
+
+  /**
+   * Provides test data for ::testLayoutSectionFormatter().
+   */
+  public function providerTestLayoutSectionFormatter() {
+    $data = [];
+    $data['block_with_context'] = [
+      [
+        [
+          'layout' => 'layout_onecol',
+          'section' => [
+            'content' => [
+              'baz' => [
+                'block' => [
+                  'id' => 'test_context_aware',
+                  'context_mapping' => [
+                    'user' => '@user.current_user_context:current_user',
+                  ],
+                ],
+              ],
+            ],
+          ],
+        ],
+      ],
+      [
+        '.layout--onecol',
+        '#test_context_aware--username',
+      ],
+      [
+        'foobar',
+        'User context found',
+      ],
+      'user',
+      'user:2',
+      'UNCACHEABLE',
+    ];
+    $data['single_section_single_block'] = [
+      [
+        [
+          'layout' => 'layout_onecol',
+          'section' => [
+            'content' => [
+              'baz' => [
+                'block' => [
+                  'id' => 'system_powered_by_block',
+                ],
+              ],
+            ],
+          ],
+        ],
+      ],
+      '.layout--onecol',
+      'Powered by',
+      '',
+      '',
+      'MISS',
+    ];
+    $data['multiple_sections'] = [
+      [
+        [
+          'layout' => 'layout_onecol',
+          'section' => [
+            'content' => [
+              'baz' => [
+                'block' => [
+                  'id' => 'system_powered_by_block',
+                ],
+              ],
+            ],
+          ],
+        ],
+        [
+          'layout' => 'layout_twocol',
+          'section' => [
+            'first' => [
+              'foo' => [
+                'block' => [
+                  'id' => 'test_block_instantiation',
+                  'display_message' => 'foo text',
+                ],
+              ],
+            ],
+            'second' => [
+              'bar' => [
+                'block' => [
+                  'id' => 'test_block_instantiation',
+                  'display_message' => 'bar text',
+                ],
+              ],
+            ],
+          ],
+        ],
+      ],
+      [
+        '.layout--onecol',
+        '.layout--twocol',
+      ],
+      [
+        'Powered by',
+        'foo text',
+        'bar text',
+      ],
+      'user.permissions',
+      '',
+      'MISS',
+    ];
+    return $data;
+  }
+
+  /**
+   * Tests layout_section formatter output.
+   *
+   * @dataProvider providerTestLayoutSectionFormatter
+   */
+  public function testLayoutSectionFormatter($layout_data, $expected_selector, $expected_content, $expected_cache_contexts, $expected_cache_tags, $expected_dynamic_cache) {
+    $this->createSectionNode($layout_data);
+
+    $this->drupalGet('node/1');
+    $this->assertLayoutSection($expected_selector, $expected_content, $expected_cache_contexts, $expected_cache_tags, $expected_dynamic_cache);
+
+    $this->drupalGet('node/1/layout');
+    $this->assertLayoutSection($expected_selector, $expected_content, $expected_cache_contexts, $expected_cache_tags, 'UNCACHEABLE');
+  }
+
+  /**
+   * Tests the access checking of the section formatter.
+   */
+  public function testLayoutSectionFormatterAccess() {
+    $this->createSectionNode([
+      [
+        'layout' => 'layout_onecol',
+        'section' => [
+          'content' => [
+            'baz' => [
+              'block' => [
+                'id' => 'test_access',
+              ],
+            ],
+          ],
+        ],
+      ],
+    ]);
+
+    // Restrict access to the block.
+    $this->container->get('state')->set('test_block_access', FALSE);
+
+    $this->drupalGet('node/1');
+    $this->assertLayoutSection('.layout--onecol', NULL, '', '', 'UNCACHEABLE');
+    // Ensure the block was not rendered.
+    $this->assertSession()->pageTextNotContains('Hello test world');
+
+    // Grant access to the block, and ensure it was rendered.
+    $this->container->get('state')->set('test_block_access', TRUE);
+    $this->drupalGet('node/1');
+    $this->assertLayoutSection('.layout--onecol', 'Hello test world', '', '', 'UNCACHEABLE');
+  }
+
+  /**
+   * Tests the multilingual support of the section formatter.
+   */
+  public function testMultilingualLayoutSectionFormatter() {
+    $this->container->get('module_installer')->install(['content_translation']);
+    $this->rebuildContainer();
+
+    ConfigurableLanguage::createFromLangcode('es')->save();
+    $this->container->get('content_translation.manager')->setEnabled('node', 'bundle_with_section_field', TRUE);
+
+    $entity = $this->createSectionNode([
+      [
+        'layout' => 'layout_onecol',
+        'section' => [
+          'content' => [
+            'baz' => [
+              'block' => [
+                'id' => 'system_powered_by_block',
+              ],
+            ],
+          ],
+        ],
+      ],
+    ]);
+    $entity->addTranslation('es', [
+      'title' => 'Translated node title',
+      $this->fieldName => [
+        [
+          'layout' => 'layout_twocol',
+          'section' => [
+            'first' => [
+              'foo' => [
+                'block' => [
+                  'id' => 'test_block_instantiation',
+                  'display_message' => 'foo text',
+                ],
+              ],
+            ],
+            'second' => [
+              'bar' => [
+                'block' => [
+                  'id' => 'test_block_instantiation',
+                  'display_message' => 'bar text',
+                ],
+              ],
+            ],
+          ],
+        ],
+      ],
+    ]);
+    $entity->save();
+
+    $this->drupalGet('node/1');
+    $this->assertLayoutSection('.layout--onecol', 'Powered by');
+    $this->drupalGet('es/node/1');
+    $this->assertLayoutSection('.layout--twocol', ['foo text', 'bar text']);
+  }
+
+  /**
+   * Ensures that the entity title is displayed.
+   */
+  public function testLayoutPageTitle() {
+    $this->drupalPlaceBlock('page_title_block');
+    $this->createSectionNode([]);
+
+    $this->drupalGet('node/1/layout');
+    $this->assertSession()->titleEquals('Edit layout for The node title | Drupal');
+    $this->assertEquals('Edit layout for The node title', $this->cssSelect('h1.page-title')[0]->getText());
+  }
+
+  /**
+   * Tests that no Layout link shows without a section field.
+   */
+  public function testLayoutUrlNoSectionField() {
+    $this->createNode([
+      'type' => 'bundle_without_section_field',
+      'title' => 'The node title',
+      'body' => [
+        [
+          'value' => 'The node body',
+        ],
+      ],
+    ]);
+    $this->drupalGet('node/1/layout');
+    $this->assertSession()->statusCodeEquals(403);
+  }
+
+  /**
+   * Asserts the output of a layout section.
+   *
+   * @param string|array $expected_selector
+   *   A selector or list of CSS selectors to find.
+   * @param string|array $expected_content
+   *   A string or list of strings to find.
+   * @param string $expected_cache_contexts
+   *   A string of cache contexts to be found in the header.
+   * @param string $expected_cache_tags
+   *   A string of cache tags to be found in the header.
+   * @param string $expected_dynamic_cache
+   *   The expected dynamic cache header. Either 'HIT', 'MISS' or 'UNCACHEABLE'.
+   */
+  protected function assertLayoutSection($expected_selector, $expected_content, $expected_cache_contexts = '', $expected_cache_tags = '', $expected_dynamic_cache = 'MISS') {
+    $assert_session = $this->assertSession();
+    // Find the given selector.
+    foreach ((array) $expected_selector as $selector) {
+      $element = $this->cssSelect($selector);
+      $this->assertNotEmpty($element);
+    }
+
+    // Find the given content.
+    foreach ((array) $expected_content as $content) {
+      $assert_session->pageTextContains($content);
+    }
+    if ($expected_cache_contexts) {
+      $assert_session->responseHeaderContains('X-Drupal-Cache-Contexts', $expected_cache_contexts);
+    }
+    if ($expected_cache_tags) {
+      $assert_session->responseHeaderContains('X-Drupal-Cache-Tags', $expected_cache_tags);
+    }
+    $assert_session->responseHeaderEquals('X-Drupal-Dynamic-Cache', $expected_dynamic_cache);
+  }
+
+  /**
+   * Creates a node with a section field.
+   *
+   * @param array $section_values
+   *   An array of values for a section field.
+   *
+   * @return \Drupal\node\NodeInterface
+   *   The node object.
+   */
+  protected function createSectionNode(array $section_values) {
+    return $this->createNode([
+      'type' => 'bundle_with_section_field',
+      'title' => 'The node title',
+      'body' => [
+        [
+          'value' => 'The node body',
+        ],
+      ],
+      $this->fieldName => $section_values,
+    ]);
+  }
+
+}
diff --git a/core/modules/layout_builder/tests/src/FunctionalJavascript/LayoutBuilderTest.php b/core/modules/layout_builder/tests/src/FunctionalJavascript/LayoutBuilderTest.php
new file mode 100644
index 0000000..8d9008f
--- /dev/null
+++ b/core/modules/layout_builder/tests/src/FunctionalJavascript/LayoutBuilderTest.php
@@ -0,0 +1,294 @@
+<?php
+
+namespace Drupal\Tests\layout_builder\FunctionalJavascript;
+
+use Drupal\block_content\Entity\BlockContent;
+use Drupal\block_content\Entity\BlockContentType;
+use Drupal\FunctionalJavascriptTests\JavascriptTestBase;
+
+/**
+ * Tests the Layout Builder UI.
+ *
+ * @group layout_builder
+ */
+class LayoutBuilderTest extends JavascriptTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = [
+    'layout_builder',
+    'node',
+    'block_content',
+    'field_ui',
+    'layout_test',
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->drupalPlaceBlock('local_tasks_block');
+
+    $bundle = BlockContentType::create([
+      'id' => 'basic',
+      'label' => 'Basic',
+    ]);
+    $bundle->save();
+    block_content_add_body_field($bundle->id());
+    BlockContent::create([
+      'info' => 'My custom block',
+      'type' => 'basic',
+      'body' => [
+        [
+          'value' => 'This is the block content',
+          'format' => filter_default_format(),
+        ],
+      ],
+    ])->save();
+
+    $this->createContentType(['type' => 'bundle_with_section_field']);
+    $this->createNode([
+      'type' => 'bundle_with_section_field',
+      'title' => 'The node title',
+      'body' => [
+        [
+          'value' => 'The node body',
+        ],
+      ],
+    ]);
+
+    $this->drupalLogin($this->drupalCreateUser([
+      'access contextual links',
+      'configure any layout',
+      'administer node display',
+    ], 'foobar'));
+
+    // Enable layout support.
+    $this->drupalGet('admin/structure/types/manage/bundle_with_section_field/display');
+    $page = $this->getSession()->getPage();
+    $page->checkField('layout[allow_custom]');
+    $page->pressButton('Save');
+  }
+
+  /**
+   * Tests the Layout Builder UI.
+   */
+  public function test() {
+    $assert_session = $this->assertSession();
+    $page = $this->getSession()->getPage();
+
+    // Ensure the block is not displayed initially.
+    $this->drupalGet('node/1');
+    $assert_session->pageTextNotContains('Powered by Drupal');
+
+    // Enter the layout editing mode.
+    $this->clickLink('Layout');
+    $assert_session->linkExists('Add Section');
+    $assert_session->linkNotExists('Add Block');
+
+    // Add a new section.
+    $this->clickLink('Add Section');
+    $assert_session->assertWaitOnAjaxRequest();
+    $assert_session->elementExists('css', '#drupal-off-canvas');
+
+    $this->clickLink('One column');
+    $assert_session->assertWaitOnAjaxRequest();
+    $assert_session->elementNotExists('css', '#drupal-off-canvas');
+
+    $assert_session->linkExists('Add Section');
+    $assert_session->linkExists('Add Block');
+
+    // Add a new block.
+    $this->clickLink('Add Block');
+    $assert_session->assertWaitOnAjaxRequest();
+
+    $assert_session->elementExists('css', '#drupal-off-canvas');
+
+    $this->clickLink('Powered by Drupal');
+    $assert_session->assertWaitOnAjaxRequest();
+    $assert_session->elementExists('css', '#drupal-off-canvas');
+
+    $page->fillField('settings[label]', 'This is the label');
+    $page->checkField('settings[label_display]');
+
+    // Save the new block, and ensure it is displayed on the page.
+    $page->pressButton('Add Block');
+    $assert_session->assertWaitOnAjaxRequest();
+    $assert_session->elementNotExists('css', '#drupal-off-canvas');
+
+    $assert_session->addressEquals('node/1/layout');
+    $assert_session->pageTextContains('Powered by Drupal');
+    $assert_session->pageTextContains('This is the label');
+
+    // Until the layout is saved, the new block is not visible on the node page.
+    $this->drupalGet('node/1');
+    $assert_session->pageTextNotContains('Powered by Drupal');
+
+    // When returning to the layout edit mode, the new block is visible.
+    $this->drupalGet('node/1/layout');
+    $assert_session->pageTextContains('Powered by Drupal');
+
+    // Save the layout, and the new block is visible.
+    $this->clickLink('Save Layout');
+    $assert_session->addressEquals('node/1');
+    $assert_session->pageTextContains('Powered by Drupal');
+    $assert_session->pageTextContains('This is the label');
+    $assert_session->elementExists('css', '.layout');
+
+    // Drag one block from one region to another.
+    $this->drupalGet('node/1/layout');
+    $this->clickLink('Add Section');
+    $assert_session->assertWaitOnAjaxRequest();
+
+    $this->clickLink('Two column');
+    $assert_session->assertWaitOnAjaxRequest();
+
+    $assert_session->elementNotExists('css', '.layout__region--second .block-system-powered-by-block');
+    $assert_session->elementTextNotContains('css', '.layout__region--second', 'Powered by Drupal');
+    // Drag the block from one layout to another.
+    $page->find('css', '.layout__region--content .block-system-powered-by-block')->dragTo($page->find('css', '.layout__region--second'));
+    $assert_session->assertWaitOnAjaxRequest();
+    // Ensure the drag succeeded.
+    $assert_session->elementExists('css', '.layout__region--second .block-system-powered-by-block');
+    $assert_session->elementTextContains('css', '.layout__region--second', 'Powered by Drupal');
+    // Ensure the drag persisted after reload.
+    $this->drupalGet('node/1/layout');
+    $assert_session->elementExists('css', '.layout__region--second .block-system-powered-by-block');
+    $assert_session->elementTextContains('css', '.layout__region--second', 'Powered by Drupal');
+    // Ensure the drag persisted after save.
+    $this->clickLink('Save Layout');
+    $assert_session->elementExists('css', '.layout__region--second .block-system-powered-by-block');
+    $assert_session->elementTextContains('css', '.layout__region--second', 'Powered by Drupal');
+
+    // Configure a block.
+    $this->drupalGet('node/1/layout');
+    $assert_session->assertWaitOnAjaxRequest();
+    $this->toggleContextualTriggerVisibility('.block-system-powered-by-block');
+    $assert_session->assertWaitOnAjaxRequest();
+    $page->find('css', '.block-system-powered-by-block .contextual .trigger')->click();
+    $assert_session->assertWaitOnAjaxRequest();
+
+    $this->clickLink('Configure');
+    $assert_session->assertWaitOnAjaxRequest();
+    $assert_session->elementExists('css', '#drupal-off-canvas');
+
+    $page->fillField('settings[label]', 'This is the new label');
+    $page->pressButton('Update');
+    $assert_session->assertWaitOnAjaxRequest();
+    $assert_session->elementNotExists('css', '#drupal-off-canvas');
+
+    $assert_session->addressEquals('node/1/layout');
+    $assert_session->pageTextContains('Powered by Drupal');
+    $assert_session->pageTextContains('This is the new label');
+    $assert_session->pageTextNotContains('This is the label');
+
+    // Remove a block.
+    $this->drupalGet('node/1/layout');
+
+    $this->toggleContextualTriggerVisibility('.block-system-powered-by-block');
+    $page->find('css', '.block-system-powered-by-block .contextual .trigger')->click();
+    $this->clickLink('Remove block');
+    $assert_session->assertWaitOnAjaxRequest();
+    $assert_session->elementExists('css', '#drupal-off-canvas');
+
+    $page->pressButton('Remove');
+    $assert_session->assertWaitOnAjaxRequest();
+    $assert_session->elementNotExists('css', '#drupal-off-canvas');
+
+    $assert_session->pageTextNotContains('Powered by Drupal');
+    $assert_session->linkExists('Add Block');
+    $assert_session->addressEquals('node/1/layout');
+
+    $this->clickLink('Save Layout');
+    $assert_session->elementExists('css', '.layout');
+
+    // Test deriver-based blocks.
+    $this->drupalGet('node/1/layout');
+    $this->clickLink('Add Block');
+    $assert_session->assertWaitOnAjaxRequest();
+
+    $this->clickLink('My custom block');
+    $assert_session->assertWaitOnAjaxRequest();
+
+    $page->pressButton('Add Block');
+    $assert_session->assertWaitOnAjaxRequest();
+    $assert_session->pageTextContains('This is the block content');
+
+    // Remove both sections.
+    $this->clickLink('Remove section');
+    $assert_session->assertWaitOnAjaxRequest();
+
+    $page->pressButton('Remove');
+    $assert_session->assertWaitOnAjaxRequest();
+
+    $this->clickLink('Remove section');
+    $assert_session->assertWaitOnAjaxRequest();
+
+    $page->pressButton('Remove');
+    $assert_session->assertWaitOnAjaxRequest();
+
+    $assert_session->pageTextNotContains('This is the block content');
+    $assert_session->linkNotExists('Add Block');
+    $this->clickLink('Save Layout');
+    $assert_session->elementNotExists('css', '.layout');
+  }
+
+  /**
+   * Tests configurable layouts.
+   */
+  public function testConfigurableLayouts() {
+    $assert_session = $this->assertSession();
+    $page = $this->getSession()->getPage();
+
+    $this->drupalGet('node/1/layout');
+    $this->clickLink('Add Section');
+    $assert_session->assertWaitOnAjaxRequest();
+    $assert_session->elementExists('css', '#drupal-off-canvas');
+
+    $this->clickLink('One column');
+    $assert_session->assertWaitOnAjaxRequest();
+
+    // Add another section.
+    $this->clickLink('Add Section');
+    $assert_session->assertWaitOnAjaxRequest();
+    $assert_session->elementExists('css', '#drupal-off-canvas');
+
+    $this->clickLink('Layout plugin (with settings)');
+    $assert_session->assertWaitOnAjaxRequest();
+    $assert_session->fieldExists('layout_settings[setting_1]');
+    $page->pressButton('Add section');
+    $assert_session->assertWaitOnAjaxRequest();
+
+    $assert_session->elementNotExists('css', '#drupal-off-canvas');
+    $assert_session->pageTextContains('Default');
+    $assert_session->linkExists('Add Block');
+
+    // Configure the existing section.
+    $this->clickLink('Configure section');
+    $assert_session->assertWaitOnAjaxRequest();
+    $page->fillField('layout_settings[setting_1]', 'Test setting value');
+    $page->pressButton('Update');
+    $assert_session->assertWaitOnAjaxRequest();
+    $assert_session->elementNotExists('css', '#drupal-off-canvas');
+    $assert_session->pageTextContains('Test setting value');
+  }
+
+  /**
+   * Toggles the visibility of a contextual trigger.
+   *
+   * @todo Remove this function when related trait added in
+   *   https://www.drupal.org/node/2821724.
+   *
+   * @param string $selector
+   *   The selector for the element that contains the contextual link.
+   */
+  protected function toggleContextualTriggerVisibility($selector) {
+    // Hovering over the element itself with should be enough, but does not
+    // work. Manually remove the visually-hidden class.
+    $this->getSession()->executeScript("jQuery('{$selector} .contextual .trigger').toggleClass('visually-hidden');");
+  }
+
+}
diff --git a/core/modules/layout_builder/tests/src/Kernel/LayoutSectionItemTest.php b/core/modules/layout_builder/tests/src/Kernel/LayoutSectionItemTest.php
new file mode 100644
index 0000000..76d7ea7
--- /dev/null
+++ b/core/modules/layout_builder/tests/src/Kernel/LayoutSectionItemTest.php
@@ -0,0 +1,89 @@
+<?php
+
+namespace Drupal\Tests\layout_builder\Kernel;
+
+use Drupal\Core\Field\FieldItemInterface;
+use Drupal\Core\Field\FieldItemListInterface;
+use Drupal\entity_test\Entity\EntityTest;
+use Drupal\layout_builder\LayoutSectionItemInterface;
+use Drupal\layout_builder\Field\LayoutSectionItemListInterface;
+use Drupal\Tests\field\Kernel\FieldKernelTestBase;
+
+/**
+ * Tests the field type for Layout Sections.
+ *
+ * @group layout_builder
+ */
+class LayoutSectionItemTest extends FieldKernelTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = ['layout_builder', 'layout_discovery'];
+
+  /**
+   * Tests using entity fields of the layout section field type.
+   */
+  public function testLayoutSectionItem() {
+    layout_builder_add_layout_section_field('entity_test', 'entity_test');
+
+    $entity = EntityTest::create();
+    /** @var \Drupal\layout_builder\Field\LayoutSectionItemListInterface $field_list */
+    $field_list = $entity->layout_builder__layout;
+
+    // Test sample item generation.
+    $field_list->generateSampleItems();
+    $this->entityValidateAndSave($entity);
+
+    $field = $field_list->get(0);
+    $this->assertInstanceOf(LayoutSectionItemInterface::class, $field);
+    $this->assertInstanceOf(FieldItemInterface::class, $field);
+    $this->assertSame('section', $field->mainPropertyName());
+    $this->assertSame('layout_onecol', $field->layout);
+    $this->assertSame([], $field->layout_settings);
+    $this->assertSame([], $field->section);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function testLayoutSectionItemList() {
+    layout_builder_add_layout_section_field('entity_test', 'entity_test');
+
+    $entity = EntityTest::create();
+    /** @var \Drupal\layout_builder\Field\LayoutSectionItemListInterface $field_list */
+    $field_list = $entity->layout_builder__layout;
+    $this->assertInstanceOf(LayoutSectionItemListInterface::class, $field_list);
+    $this->assertInstanceOf(FieldItemListInterface::class, $field_list);
+    $entity->save();
+
+    $field_list->appendItem(['layout' => 'layout_twocol']);
+    $field_list->appendItem(['layout' => 'layout_onecol']);
+    $field_list->appendItem(['layout' => 'layout_threecol_25_50_25']);
+    $this->assertSame([
+      ['layout' => 'layout_twocol'],
+      ['layout' => 'layout_onecol'],
+      ['layout' => 'layout_threecol_25_50_25'],
+    ], $field_list->getValue());
+
+    $field_list->addItem(1, ['layout' => 'layout_threecol_33_34_33']);
+    $this->assertSame([
+      ['layout' => 'layout_twocol'],
+      ['layout' => 'layout_threecol_33_34_33'],
+      ['layout' => 'layout_onecol'],
+      ['layout' => 'layout_threecol_25_50_25'],
+    ], $field_list->getValue());
+
+    $field_list->addItem($field_list->count(), ['layout' => 'layout_twocol_bricks']);
+    $this->assertSame([
+      ['layout' => 'layout_twocol'],
+      ['layout' => 'layout_threecol_33_34_33'],
+      ['layout' => 'layout_onecol'],
+      ['layout' => 'layout_threecol_25_50_25'],
+      ['layout' => 'layout_twocol_bricks'],
+    ], $field_list->getValue());
+  }
+
+}
diff --git a/core/modules/layout_builder/tests/src/Unit/LayoutSectionBuilderTest.php b/core/modules/layout_builder/tests/src/Unit/LayoutSectionBuilderTest.php
new file mode 100644
index 0000000..3e5b2ff
--- /dev/null
+++ b/core/modules/layout_builder/tests/src/Unit/LayoutSectionBuilderTest.php
@@ -0,0 +1,301 @@
+<?php
+
+namespace Drupal\Tests\layout_builder\Unit;
+
+use Drupal\Component\Plugin\Exception\PluginException;
+use Drupal\Core\Access\AccessResult;
+use Drupal\Core\Block\BlockManagerInterface;
+use Drupal\Core\Block\BlockPluginInterface;
+use Drupal\Core\Cache\Cache;
+use Drupal\Core\Layout\LayoutInterface;
+use Drupal\Core\Layout\LayoutPluginManagerInterface;
+use Drupal\Core\Plugin\Context\ContextHandlerInterface;
+use Drupal\Core\Plugin\Context\ContextRepositoryInterface;
+use Drupal\Core\Plugin\ContextAwarePluginInterface;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\layout_builder\LayoutSectionBuilder;
+use Drupal\Tests\UnitTestCase;
+use Prophecy\Argument;
+
+/**
+ * @coversDefaultClass \Drupal\layout_builder\LayoutSectionBuilder
+ * @group layout_builder
+ */
+class LayoutSectionBuilderTest extends UnitTestCase {
+
+  /**
+   * The current user.
+   *
+   * @var \Drupal\Core\Session\AccountInterface
+   */
+  protected $account;
+
+  /**
+   * The layout plugin manager.
+   *
+   * @var \Drupal\Core\Layout\LayoutPluginManagerInterface
+   */
+  protected $layoutPluginManager;
+
+  /**
+   * The block plugin manager.
+   *
+   * @var \Drupal\Core\Block\BlockManagerInterface
+   */
+  protected $blockManager;
+
+  /**
+   * The plugin context handler.
+   *
+   * @var \Drupal\Core\Plugin\Context\ContextHandlerInterface
+   */
+  protected $contextHandler;
+
+  /**
+   * The context manager service.
+   *
+   * @var \Drupal\Core\Plugin\Context\ContextRepositoryInterface
+   */
+  protected $contextRepository;
+
+  /**
+   * The object under test.
+   *
+   * @var \Drupal\layout_builder\LayoutSectionBuilder
+   */
+  protected $layoutSectionBuilder;
+
+  /**
+   * The layout plugin.
+   *
+   * @var \Drupal\Core\Layout\LayoutInterface
+   */
+  protected $layout;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->account = $this->prophesize(AccountInterface::class);
+    $this->layoutPluginManager = $this->prophesize(LayoutPluginManagerInterface::class);
+    $this->blockManager = $this->prophesize(BlockManagerInterface::class);
+    $this->contextHandler = $this->prophesize(ContextHandlerInterface::class);
+    $this->contextRepository = $this->prophesize(ContextRepositoryInterface::class);
+    $this->layoutSectionBuilder = new LayoutSectionBuilder($this->account->reveal(), $this->layoutPluginManager->reveal(), $this->blockManager->reveal(), $this->contextHandler->reveal(), $this->contextRepository->reveal());
+
+    $this->layout = $this->prophesize(LayoutInterface::class);
+    $this->layoutPluginManager->createInstance('layout_onecol', [])->willReturn($this->layout->reveal());
+  }
+
+  /**
+   * @covers ::buildSection
+   */
+  public function testBuildSection() {
+    $block_content = ['#markup' => 'The block content.'];
+    $render_array = [
+      '#theme' => 'block',
+      '#weight' => 0,
+      '#configuration' => [],
+      '#plugin_id' => 'block_plugin_id',
+      '#base_plugin_id' => 'block_plugin_id',
+      '#derivative_plugin_id' => NULL,
+      'content' => $block_content,
+    ];
+    $this->layout->build(['content' => ['some_uuid' => $render_array]])->willReturnArgument(0);
+
+    $block = $this->prophesize(BlockPluginInterface::class);
+    $this->blockManager->createInstance('block_plugin_id', ['id' => 'block_plugin_id'])->willReturn($block->reveal());
+
+    $access_result = AccessResult::allowed();
+    $block->access($this->account->reveal(), TRUE)->willReturn($access_result);
+    $block->build()->willReturn($block_content);
+    $block->getCacheContexts()->willReturn([]);
+    $block->getCacheTags()->willReturn([]);
+    $block->getCacheMaxAge()->willReturn(Cache::PERMANENT);
+    $block->getPluginId()->willReturn('block_plugin_id');
+    $block->getBaseId()->willReturn('block_plugin_id');
+    $block->getDerivativeId()->willReturn(NULL);
+    $block->getConfiguration()->willReturn([]);
+
+    $section = [
+      'content' => [
+        'some_uuid' => [
+          'block' => [
+            'id' => 'block_plugin_id',
+          ],
+        ],
+      ],
+    ];
+    $expected = [
+      '#cache' => [
+        'contexts' => [],
+        'tags' => [],
+        'max-age' => -1,
+      ],
+      'content' => [
+        'some_uuid' => $render_array,
+      ],
+    ];
+    $result = $this->layoutSectionBuilder->buildSection('layout_onecol', [], $section);
+    $this->assertEquals($expected, $result);
+  }
+
+  /**
+   * @covers ::buildSection
+   */
+  public function testBuildSectionAccessDenied() {
+    $this->layout->build([])->willReturn([]);
+
+    $block = $this->prophesize(BlockPluginInterface::class);
+    $this->blockManager->createInstance('block_plugin_id', ['id' => 'block_plugin_id'])->willReturn($block->reveal());
+
+    $access_result = AccessResult::forbidden();
+    $block->access($this->account->reveal(), TRUE)->willReturn($access_result);
+    $block->build()->shouldNotBeCalled();
+
+    $section = [
+      'content' => [
+        'some_uuid' => [
+          'block' => [
+            'id' => 'block_plugin_id',
+          ],
+        ],
+      ],
+    ];
+    $expected = [
+      '#cache' => [
+        'contexts' => [],
+        'tags' => [],
+        'max-age' => -1,
+      ],
+    ];
+    $result = $this->layoutSectionBuilder->buildSection('layout_onecol', [], $section);
+    $this->assertEquals($expected, $result);
+  }
+
+  /**
+   * @covers ::buildSection
+   */
+  public function testBuildSectionEmpty() {
+    $this->layout->build([])->willReturn([]);
+
+    $section = [];
+    $expected = [
+      '#cache' => [
+        'contexts' => [],
+        'tags' => [],
+        'max-age' => -1,
+      ],
+    ];
+    $result = $this->layoutSectionBuilder->buildSection('layout_onecol', [], $section);
+    $this->assertEquals($expected, $result);
+  }
+
+  /**
+   * @covers ::buildSection
+   * @covers ::getBlock
+   */
+  public function testContextAwareBlock() {
+    $render_array = [
+      '#theme' => 'block',
+      '#weight' => 0,
+      '#configuration' => [],
+      '#plugin_id' => 'block_plugin_id',
+      '#base_plugin_id' => 'block_plugin_id',
+      '#derivative_plugin_id' => NULL,
+      'content' => [],
+    ];
+    $this->layout->build(['content' => ['some_uuid' => $render_array]])->willReturnArgument(0);
+
+    $block = $this->prophesize(BlockPluginInterface::class)->willImplement(ContextAwarePluginInterface::class);
+    $this->blockManager->createInstance('block_plugin_id', ['id' => 'block_plugin_id'])->willReturn($block->reveal());
+
+    $access_result = AccessResult::allowed();
+    $block->access($this->account->reveal(), TRUE)->willReturn($access_result);
+    $block->build()->willReturn([]);
+    $block->getCacheContexts()->willReturn([]);
+    $block->getCacheTags()->willReturn([]);
+    $block->getCacheMaxAge()->willReturn(Cache::PERMANENT);
+    $block->getContextMapping()->willReturn([]);
+    $block->getPluginId()->willReturn('block_plugin_id');
+    $block->getBaseId()->willReturn('block_plugin_id');
+    $block->getDerivativeId()->willReturn(NULL);
+    $block->getConfiguration()->willReturn([]);
+
+    $this->contextRepository->getRuntimeContexts([])->willReturn([]);
+    $this->contextHandler->applyContextMapping($block->reveal(), [])->shouldBeCalled();
+
+    $section = [
+      'content' => [
+        'some_uuid' => [
+          'block' => [
+            'id' => 'block_plugin_id',
+          ],
+        ],
+      ],
+    ];
+    $expected = [
+      '#cache' => [
+        'contexts' => [],
+        'tags' => [],
+        'max-age' => -1,
+      ],
+      'content' => [
+        'some_uuid' => $render_array,
+      ],
+    ];
+    $result = $this->layoutSectionBuilder->buildSection('layout_onecol', [], $section);
+    $this->assertEquals($expected, $result);
+  }
+
+  /**
+   * @covers ::buildSection
+   * @covers ::getBlock
+   */
+  public function testBuildSectionMissingPluginId() {
+    $section = [
+      'content' => [
+        'some_uuid' => [
+          'block' => [],
+        ],
+      ],
+    ];
+    $this->setExpectedException(PluginException::class, 'No plugin ID specified for block with "some_uuid" UUID');
+    $this->layoutSectionBuilder->buildSection('layout_onecol', [], $section);
+  }
+
+  /**
+   * @covers ::buildSection
+   *
+   * @dataProvider providerTestBuildSectionMalformedData
+   */
+  public function testBuildSectionMalformedData($section, $message) {
+    $this->layout->build(Argument::type('array'))->willReturnArgument(0);
+    $this->layout->getPluginId()->willReturn('the_plugin_id');
+    $this->setExpectedException(\InvalidArgumentException::class, $message);
+    $this->layoutSectionBuilder->buildSection('layout_onecol', [], $section);
+  }
+
+  /**
+   * Provides test data for ::testBuildSectionMalformedData().
+   */
+  public function providerTestBuildSectionMalformedData() {
+    $data = [];
+    $data['invalid_region'] = [
+      ['content' => 'bar'],
+      'The "content" region in the "the_plugin_id" layout has invalid configuration',
+    ];
+    $data['invalid_configuration'] = [
+      ['content' => ['some_uuid' => 'bar']],
+      'The block with UUID of "some_uuid" has invalid configuration',
+    ];
+    $data['invalid_blocks'] = [
+      ['content' => ['some_uuid' => []]],
+      'The block with UUID of "some_uuid" has invalid configuration',
+    ];
+    return $data;
+  }
+
+}
diff --git a/core/modules/layout_builder/tests/src/Unit/LayoutTempstoreRepositoryTest.php b/core/modules/layout_builder/tests/src/Unit/LayoutTempstoreRepositoryTest.php
new file mode 100644
index 0000000..919b02c
--- /dev/null
+++ b/core/modules/layout_builder/tests/src/Unit/LayoutTempstoreRepositoryTest.php
@@ -0,0 +1,110 @@
+<?php
+
+namespace Drupal\Tests\layout_builder\Unit;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityStorageInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Entity\RevisionableInterface;
+use Drupal\Core\Language\Language;
+use Drupal\layout_builder\LayoutTempstoreRepository;
+use Drupal\Tests\UnitTestCase;
+use Drupal\user\SharedTempStore;
+use Drupal\user\SharedTempStoreFactory;
+
+/**
+ * @coversDefaultClass \Drupal\layout_builder\LayoutTempstoreRepository
+ * @group layout_builder
+ */
+class LayoutTempstoreRepositoryTest extends UnitTestCase {
+
+  /**
+   * @covers ::getFromId
+   * @covers ::get
+   * @covers ::generateTempstoreId
+   */
+  public function testGetFromIdEmptyTempstore() {
+    $tempstore = $this->prophesize(SharedTempStore::class);
+    $tempstore->get('the_entity_id.en')->shouldBeCalled();
+
+    $tempstore_factory = $this->prophesize(SharedTempStoreFactory::class);
+    $tempstore_factory->get('the_entity_type_id.layout_builder__layout')->willReturn($tempstore->reveal());
+
+    $entity = $this->prophesize(EntityInterface::class);
+    $entity->getEntityTypeId()->willReturn('the_entity_type_id');
+    $entity->id()->willReturn('the_entity_id');
+    $entity->language()->willReturn(new Language(['id' => 'en']));
+
+    $entity_storage = $this->prophesize(EntityStorageInterface::class);
+    $entity_storage->loadRevision('the_entity_id')->willReturn($entity->reveal());
+
+    $entity_type_manager = $this->prophesize(EntityTypeManagerInterface::class);
+    $entity_type_manager->getStorage('the_entity_type_id')->willReturn($entity_storage->reveal());
+
+    $repository = new LayoutTempstoreRepository($tempstore_factory->reveal(), $entity_type_manager->reveal());
+
+    $result = $repository->getFromId('the_entity_type_id', 'the_entity_id');
+    $this->assertSame($entity->reveal(), $result);
+  }
+
+  /**
+   * @covers ::getFromId
+   * @covers ::get
+   * @covers ::generateTempstoreId
+   */
+  public function testGetFromIdLoadedTempstore() {
+    $tempstore_entity = $this->prophesize(EntityInterface::class);
+    $tempstore = $this->prophesize(SharedTempStore::class);
+    $tempstore->get('the_entity_id.en')->willReturn(['entity' => $tempstore_entity->reveal()]);
+    $tempstore_factory = $this->prophesize(SharedTempStoreFactory::class);
+    $tempstore_factory->get('the_entity_type_id.layout_builder__layout')->willReturn($tempstore->reveal());
+
+    $entity = $this->prophesize(EntityInterface::class);
+    $entity->getEntityTypeId()->willReturn('the_entity_type_id');
+    $entity->id()->willReturn('the_entity_id');
+    $entity->language()->willReturn(new Language(['id' => 'en']));
+
+    $entity_storage = $this->prophesize(EntityStorageInterface::class);
+    $entity_storage->loadRevision('the_entity_id')->willReturn($entity->reveal());
+
+    $entity_type_manager = $this->prophesize(EntityTypeManagerInterface::class);
+    $entity_type_manager->getStorage('the_entity_type_id')->willReturn($entity_storage->reveal());
+
+    $repository = new LayoutTempstoreRepository($tempstore_factory->reveal(), $entity_type_manager->reveal());
+
+    $result = $repository->getFromId('the_entity_type_id', 'the_entity_id');
+    $this->assertSame($tempstore_entity->reveal(), $result);
+    $this->assertNotSame($entity->reveal(), $result);
+  }
+
+  /**
+   * @covers ::getFromId
+   * @covers ::get
+   * @covers ::generateTempstoreId
+   */
+  public function testGetFromIdRevisionable() {
+    $tempstore = $this->prophesize(SharedTempStore::class);
+    $tempstore->get('the_entity_id.en.the_revision_id')->shouldBeCalled();
+
+    $tempstore_factory = $this->prophesize(SharedTempStoreFactory::class);
+    $tempstore_factory->get('the_entity_type_id.layout_builder__layout')->willReturn($tempstore->reveal());
+
+    $entity = $this->prophesize(EntityInterface::class)->willImplement(RevisionableInterface::class);
+    $entity->getEntityTypeId()->willReturn('the_entity_type_id');
+    $entity->id()->willReturn('the_entity_id');
+    $entity->language()->willReturn(new Language(['id' => 'en']));
+    $entity->getRevisionId()->willReturn('the_revision_id');
+
+    $entity_storage = $this->prophesize(EntityStorageInterface::class);
+    $entity_storage->loadRevision('the_entity_id')->willReturn($entity->reveal());
+
+    $entity_type_manager = $this->prophesize(EntityTypeManagerInterface::class);
+    $entity_type_manager->getStorage('the_entity_type_id')->willReturn($entity_storage->reveal());
+
+    $repository = new LayoutTempstoreRepository($tempstore_factory->reveal(), $entity_type_manager->reveal());
+
+    $result = $repository->getFromId('the_entity_type_id', 'the_entity_id');
+    $this->assertSame($entity->reveal(), $result);
+  }
+
+}
diff --git a/core/modules/layout_discovery/layout_discovery.layouts.yml b/core/modules/layout_discovery/layout_discovery.layouts.yml
index d1b0e5a..755a96b 100644
--- a/core/modules/layout_discovery/layout_discovery.layouts.yml
+++ b/core/modules/layout_discovery/layout_discovery.layouts.yml
@@ -5,6 +5,8 @@ layout_onecol:
   library: layout_discovery/onecol
   category: 'Columns: 1'
   default_region: content
+  icon_map:
+    - [content]
   regions:
     content:
       label: Content
@@ -16,6 +18,10 @@ layout_twocol:
   library: layout_discovery/twocol
   category: 'Columns: 2'
   default_region: first
+  icon_map:
+    - [top]
+    - [first, second]
+    - [bottom]
   regions:
     top:
       label: Top
@@ -33,6 +39,12 @@ layout_twocol_bricks:
   library: layout_discovery/twocol_bricks
   category: 'Columns: 2'
   default_region: middle
+  icon_map:
+    - [top]
+    - [first_above, second_above]
+    - [middle]
+    - [first_below, second_below]
+    - [bottom]
   regions:
     top:
       label: Top
@@ -56,6 +68,10 @@ layout_threecol_25_50_25:
   library: layout_discovery/threecol_25_50_25
   category: 'Columns: 3'
   default_region: second
+  icon_map:
+    - [top]
+    - [first, second, second, third]
+    - [bottom]
   regions:
     top:
       label: Top
@@ -75,6 +91,10 @@ layout_threecol_33_34_33:
   library: layout_discovery/threecol_33_34_33
   category: 'Columns: 3'
   default_region: first
+  icon_map:
+    - [top]
+    - [first, second, third]
+    - [bottom]
   regions:
     top:
       label: Top
diff --git a/core/modules/layout_discovery/layout_discovery.services.yml b/core/modules/layout_discovery/layout_discovery.services.yml
index 1e24db4..6bb4073 100644
--- a/core/modules/layout_discovery/layout_discovery.services.yml
+++ b/core/modules/layout_discovery/layout_discovery.services.yml
@@ -2,3 +2,5 @@ services:
   plugin.manager.core.layout:
     class: Drupal\Core\Layout\LayoutPluginManager
     arguments: ['@container.namespaces', '@cache.discovery', '@module_handler', '@theme_handler']
+  layout.icon_generator:
+    class: Drupal\Core\Layout\IconGenerator
diff --git a/core/modules/settings_tray/css/off-canvas.reset.css b/core/modules/settings_tray/css/off-canvas.reset.css
index 573d8c7..2373797 100644
--- a/core/modules/settings_tray/css/off-canvas.reset.css
+++ b/core/modules/settings_tray/css/off-canvas.reset.css
@@ -12,7 +12,8 @@
 #drupal-off-canvas *:not(div),
 #drupal-off-canvas *:after,
 #drupal-off-canvas *:before {
-  all: initial;
+  /* @todo This breaks SVGs, fix in https://www.drupal.org/node/2907420. */
+  /*all: initial;*/
   box-sizing: border-box;
   text-shadow: none;
   -webkit-font-smoothing: antialiased;
diff --git a/core/tests/Drupal/KernelTests/Core/Layout/IconGeneratorTest.php b/core/tests/Drupal/KernelTests/Core/Layout/IconGeneratorTest.php
new file mode 100644
index 0000000..bc69478
--- /dev/null
+++ b/core/tests/Drupal/KernelTests/Core/Layout/IconGeneratorTest.php
@@ -0,0 +1,89 @@
+<?php
+
+namespace Drupal\KernelTests\Core\Layout;
+
+use Drupal\Core\Layout\IconGenerator;
+use Drupal\Core\Render\RenderContext;
+use Drupal\KernelTests\KernelTestBase;
+
+/**
+ * @coversDefaultClass \Drupal\Core\Layout\IconGenerator
+ * @group Layout
+ */
+class IconGeneratorTest extends KernelTestBase {
+
+  /**
+   * @covers ::generateSvgFromIconMap
+   *
+   * @dataProvider providerTestGenerateSvgFromIconMap
+   */
+  public function testGenerateSvgFromIconMap($icon_map, $expected) {
+    $renderer = $this->container->get('renderer');
+    $icon_generator = new IconGenerator();
+    $build = $icon_generator->generateSvgFromIconMap($icon_map);
+    $output = (string) $renderer->executeInRenderContext(new RenderContext(), function () use ($build, $renderer) {
+      return $renderer->render($build);
+    });
+    $this->assertSame($expected, $output);
+  }
+
+  public function providerTestGenerateSvgFromIconMap() {
+    $data = [];
+    $data['empty'][] = [];
+    $data['empty'][] = <<<'EOD'
+<svg width="250" height="300"></svg>
+
+EOD;
+
+    $data['two_column'][] = [['left', 'right']];
+    $data['two_column'][] = <<<'EOD'
+<svg width="250" height="300"><g><title>left</title>
+<rect x="0" y="0" width="120" height="295" fill="lightgray" stroke="black" stroke-width="2" />
+</g>
+<g><title>right</title>
+<rect x="125" y="0" width="120" height="295" fill="lightgray" stroke="black" stroke-width="2" />
+</g>
+</svg>
+
+EOD;
+
+    $data['stacked'][] = [
+      ['sidebar', 'top', 'top'],
+      ['sidebar', 'left', 'right'],
+      ['sidebar', 'middle', 'middle'],
+      ['footer_left', 'footer_right'],
+      ['footer_full'],
+    ];
+    $data['stacked'][] = <<<'EOD'
+<svg width="250" height="300"><g><title>sidebar</title>
+<rect x="0" y="0" width="78.333333333333" height="175" fill="lightgray" stroke="black" stroke-width="2" />
+</g>
+<g><title>top</title>
+<rect x="83.333333333333" y="0" width="161.66666666667" height="55" fill="lightgray" stroke="black" stroke-width="2" />
+</g>
+<g><title>left</title>
+<rect x="83.333333333333" y="60" width="78.333333333333" height="55" fill="lightgray" stroke="black" stroke-width="2" />
+</g>
+<g><title>right</title>
+<rect x="166.66666666667" y="60" width="78.333333333333" height="55" fill="lightgray" stroke="black" stroke-width="2" />
+</g>
+<g><title>middle</title>
+<rect x="83.333333333333" y="120" width="161.66666666667" height="55" fill="lightgray" stroke="black" stroke-width="2" />
+</g>
+<g><title>footer_left</title>
+<rect x="0" y="180" width="120" height="55" fill="lightgray" stroke="black" stroke-width="2" />
+</g>
+<g><title>footer_right</title>
+<rect x="125" y="180" width="120" height="55" fill="lightgray" stroke="black" stroke-width="2" />
+</g>
+<g><title>footer_full</title>
+<rect x="0" y="240" width="245" height="55" fill="lightgray" stroke="black" stroke-width="2" />
+</g>
+</svg>
+
+EOD;
+
+    return $data;
+  }
+
+}
