diff --git a/core/composer.json b/core/composer.json
index 53ab8fa..862b5a3 100644
--- a/core/composer.json
+++ b/core/composer.json
@@ -94,6 +94,7 @@
         "drupal/editor": "self.version",
         "drupal/entity_reference": "self.version",
         "drupal/field": "self.version",
+        "drupal/field_layout": "self.version",
         "drupal/field_ui": "self.version",
         "drupal/file": "self.version",
         "drupal/filter": "self.version",
diff --git a/core/config/schema/core.entity.schema.yml b/core/config/schema/core.entity.schema.yml
index bf0e12d..711ca0f 100644
--- a/core/config/schema/core.entity.schema.yml
+++ b/core/config/schema/core.entity.schema.yml
@@ -64,6 +64,9 @@ core.entity_view_display.*.*.*:
           weight:
             type: integer
             label: 'Weight'
+          region:
+            type: string
+            label: 'Region'
           label:
              type: string
              label: 'Label setting machine name'
@@ -115,6 +118,9 @@ core.entity_form_display.*.*.*:
           weight:
             type: integer
             label: 'Weight'
+          region:
+            type: string
+            label: 'Region'
           settings:
             type: field.widget.settings.[%parent.type]
             label: 'Settings'
diff --git a/core/includes/entity.inc b/core/includes/entity.inc
index 03d40a9..2471091 100644
--- a/core/includes/entity.inc
+++ b/core/includes/entity.inc
@@ -514,7 +514,7 @@ function entity_get_display($entity_type, $bundle, $view_mode) {
  *     'weight' => 1,
  *   ))
  *   ->setComponent('field_image', array(
- *     'type' => 'hidden',
+ *     'region' => 'hidden',
  *   ))
  *   ->save();
  * @endcode
diff --git a/core/lib/Drupal/Core/Entity/ContentEntityBase.php b/core/lib/Drupal/Core/Entity/ContentEntityBase.php
index 7c0a458..1854c26 100644
--- a/core/lib/Drupal/Core/Entity/ContentEntityBase.php
+++ b/core/lib/Drupal/Core/Entity/ContentEntityBase.php
@@ -1142,7 +1142,7 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
       $fields[$entity_type->getKey('langcode')] = BaseFieldDefinition::create('language')
         ->setLabel(new TranslatableMarkup('Language'))
         ->setDisplayOptions('view', [
-          'type' => 'hidden',
+          'region' => 'hidden',
         ])
         ->setDisplayOptions('form', [
           'type' => 'language_select',
diff --git a/core/lib/Drupal/Core/Entity/Display/EntityDisplayWithLayoutInterface.php b/core/lib/Drupal/Core/Entity/Display/EntityDisplayWithLayoutInterface.php
new file mode 100644
index 0000000..0f335e9
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/Display/EntityDisplayWithLayoutInterface.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace Drupal\Core\Entity\Display;
+
+/**
+ * Provides a common interface for entity displays that have layout.
+ */
+interface EntityDisplayWithLayoutInterface extends EntityDisplayInterface {
+
+  /**
+   * Gets the default region.
+   *
+   * @return string
+   *   The default region for this display.
+   */
+  public function getDefaultRegion();
+
+}
diff --git a/core/lib/Drupal/Core/Entity/EntityDisplayBase.php b/core/lib/Drupal/Core/Entity/EntityDisplayBase.php
index 4de891a..5448761 100644
--- a/core/lib/Drupal/Core/Entity/EntityDisplayBase.php
+++ b/core/lib/Drupal/Core/Entity/EntityDisplayBase.php
@@ -4,13 +4,13 @@
 
 use Drupal\Core\Config\Entity\ConfigEntityBase;
 use Drupal\Core\Config\Entity\ConfigEntityInterface;
+use Drupal\Core\Entity\Display\EntityDisplayWithLayoutInterface;
 use Drupal\Core\Field\FieldDefinitionInterface;
-use Drupal\Core\Entity\Display\EntityDisplayInterface;
 
 /**
  * Provides a common base class for entity view and form displays.
  */
-abstract class EntityDisplayBase extends ConfigEntityBase implements EntityDisplayInterface {
+abstract class EntityDisplayBase extends ConfigEntityBase implements EntityDisplayWithLayoutInterface {
 
   /**
    * The 'mode' for runtime EntityDisplay objects used to render entities with
@@ -154,6 +154,7 @@ public function __construct(array $values, $entity_type) {
   protected function init() {
     // Only populate defaults for "official" view modes and form modes.
     if ($this->mode !== static::CUSTOM_MODE) {
+      $default_region = $this->getDefaultRegion();
       // Fill in defaults for extra fields.
       $context = $this->displayContext == 'view' ? 'display' : $this->displayContext;
       $extra_fields = \Drupal::entityManager()->getExtraFields($this->targetEntityType, $this->bundle);
@@ -163,6 +164,7 @@ protected function init() {
           // Extra fields are visible by default unless they explicitly say so.
           if (!isset($definition['visible']) || $definition['visible'] == TRUE) {
             $this->content[$name] = array(
+              'region' => $default_region,
               'weight' => $definition['weight']
             );
           }
@@ -178,10 +180,13 @@ protected function init() {
         if (!$definition->isDisplayConfigurable($this->displayContext) || (!isset($this->content[$name]) && !isset($this->hidden[$name]))) {
           $options = $definition->getDisplayOptions($this->displayContext);
 
-          if (!empty($options['type']) && $options['type'] == 'hidden') {
+          // Check if either 'type' or 'region' is set to hidden.
+          // @todo Remove handling of 'type' in https://www.drupal.org/node/2799641.
+          if ((!empty($options['type']) && $options['type'] == 'hidden') || (!empty($options['region']) && $options['region'] == 'hidden')) {
             $this->hidden[$name] = TRUE;
           }
           elseif ($options) {
+            $options += ['region' => $default_region];
             $this->content[$name] = $this->pluginManager->prepareConfiguration($definition->getType(), $options);
           }
           // Note: (base) fields that do not specify display options are not
@@ -334,6 +339,12 @@ public function setComponent($name, array $options = array()) {
     // Ensure we always have an empty settings and array.
     $options += ['settings' => [], 'third_party_settings' => []];
 
+    // Ensure that a region is set.
+    // @todo Make 'region' required in https://www.drupal.org/node/2799641.
+    if (!isset($options['region'])) {
+      $options['region'] = (isset($options['type']) && $options['type'] == 'hidden') ? 'hidden' : $this->getDefaultRegion();
+    }
+
     $this->content[$name] = $options;
     unset($this->hidden[$name]);
     unset($this->plugins[$name]);
@@ -507,6 +518,13 @@ protected function getPluginRemovedDependencies(array $plugin_dependencies, arra
   /**
    * {@inheritdoc}
    */
+  public function getDefaultRegion() {
+    return 'content';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function __sleep() {
     // Only store the definition, not external objects or derived data.
     $keys = array_keys($this->toArray());
diff --git a/core/lib/Drupal/Core/Entity/entity.api.php b/core/lib/Drupal/Core/Entity/entity.api.php
index 60ae741..8aaec20 100644
--- a/core/lib/Drupal/Core/Entity/entity.api.php
+++ b/core/lib/Drupal/Core/Entity/entity.api.php
@@ -1629,7 +1629,7 @@ function hook_entity_form_display_alter(\Drupal\Core\Entity\Display\EntityFormDi
   // Hide the 'user_picture' field from the register form.
   if ($context['entity_type'] == 'user' && $context['form_mode'] == 'register') {
     $form_display->setComponent('user_picture', array(
-      'type' => 'hidden',
+      'region' => 'hidden',
     ));
   }
 }
diff --git a/core/lib/Drupal/Core/Field/BaseFieldDefinition.php b/core/lib/Drupal/Core/Field/BaseFieldDefinition.php
index 594f31d..456166a 100644
--- a/core/lib/Drupal/Core/Field/BaseFieldDefinition.php
+++ b/core/lib/Drupal/Core/Field/BaseFieldDefinition.php
@@ -414,7 +414,8 @@ public function setDisplayOptions($display_context, array $options) {
   public function setDisplayConfigurable($display_context, $configurable) {
     // If no explicit display options have been specified, default to 'hidden'.
     if (empty($this->definition['display'][$display_context])) {
-      $this->definition['display'][$display_context]['options'] = array('type' => 'hidden');
+      // @todo Remove handling of 'type'.
+      $this->definition['display'][$display_context]['options'] = array('type' => 'hidden', 'region' => 'hidden');
     }
     $this->definition['display'][$display_context]['configurable'] = $configurable;
     return $this;
diff --git a/core/modules/aggregator/config/install/core.entity_view_display.aggregator_feed.aggregator_feed.default.yml b/core/modules/aggregator/config/install/core.entity_view_display.aggregator_feed.aggregator_feed.default.yml
index e3c23cf..e0232cf 100644
--- a/core/modules/aggregator/config/install/core.entity_view_display.aggregator_feed.aggregator_feed.default.yml
+++ b/core/modules/aggregator/config/install/core.entity_view_display.aggregator_feed.aggregator_feed.default.yml
@@ -11,20 +11,26 @@ content:
   checked:
     type: timestamp_ago
     weight: 1
+    region: content
     settings: {  }
     third_party_settings: {  }
     label: inline
   description:
     weight: 3
+    region: content
   feed_icon:
     weight: 5
+    region: content
   image:
     weight: 2
+    region: content
   items:
     weight: 0
+    region: content
   link:
     type: uri_link
     weight: 4
+    region: content
     settings: {  }
     third_party_settings: {  }
     label: inline
diff --git a/core/modules/aggregator/config/install/core.entity_view_display.aggregator_feed.aggregator_feed.summary.yml b/core/modules/aggregator/config/install/core.entity_view_display.aggregator_feed.aggregator_feed.summary.yml
index 40425f2..5e5e468 100644
--- a/core/modules/aggregator/config/install/core.entity_view_display.aggregator_feed.aggregator_feed.summary.yml
+++ b/core/modules/aggregator/config/install/core.entity_view_display.aggregator_feed.aggregator_feed.summary.yml
@@ -12,8 +12,10 @@ mode: summary
 content:
   items:
     weight: 0
+    region: content
   more_link:
     weight: 1
+    region: content
 hidden:
   checked: true
   description: true
diff --git a/core/modules/aggregator/config/install/core.entity_view_display.aggregator_item.aggregator_item.summary.yml b/core/modules/aggregator/config/install/core.entity_view_display.aggregator_item.aggregator_item.summary.yml
index 837bee0..8e29395 100644
--- a/core/modules/aggregator/config/install/core.entity_view_display.aggregator_item.aggregator_item.summary.yml
+++ b/core/modules/aggregator/config/install/core.entity_view_display.aggregator_item.aggregator_item.summary.yml
@@ -12,6 +12,7 @@ mode: summary
 content:
   timestamp:
     weight: 0
+    region: content
 hidden:
   author: true
   description: true
diff --git a/core/modules/aggregator/src/Entity/Item.php b/core/modules/aggregator/src/Entity/Item.php
index 2a2c195..969e4e4 100644
--- a/core/modules/aggregator/src/Entity/Item.php
+++ b/core/modules/aggregator/src/Entity/Item.php
@@ -76,7 +76,7 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
       ->setLabel(t('Link'))
       ->setDescription(t('The link of the feed item.'))
       ->setDisplayOptions('view', array(
-        'type' => 'hidden',
+        'region' => 'hidden',
       ))
       ->setDisplayConfigurable('view', TRUE);
 
diff --git a/core/modules/book/config/install/core.entity_form_display.node.book.default.yml b/core/modules/book/config/install/core.entity_form_display.node.book.default.yml
index 1ec4eb1..58aba45 100644
--- a/core/modules/book/config/install/core.entity_form_display.node.book.default.yml
+++ b/core/modules/book/config/install/core.entity_form_display.node.book.default.yml
@@ -14,6 +14,7 @@ content:
   body:
     type: text_textarea_with_summary
     weight: 26
+    region: content
     settings:
       rows: 9
       summary_rows: 3
@@ -22,6 +23,7 @@ content:
   created:
     type: datetime_timestamp
     weight: 10
+    region: content
     settings: {  }
     third_party_settings: {  }
   promote:
@@ -29,16 +31,19 @@ content:
     settings:
       display_label: true
     weight: 15
+    region: content
     third_party_settings: {  }
   sticky:
     type: boolean_checkbox
     settings:
       display_label: true
     weight: 16
+    region: content
     third_party_settings: {  }
   title:
     type: string_textfield
     weight: -5
+    region: content
     settings:
       size: 60
       placeholder: ''
@@ -46,6 +51,7 @@ content:
   uid:
     type: entity_reference_autocomplete
     weight: 5
+    region: content
     settings:
       match_operator: CONTAINS
       size: 60
diff --git a/core/modules/book/config/install/core.entity_view_display.node.book.default.yml b/core/modules/book/config/install/core.entity_view_display.node.book.default.yml
index 729516e..d6ef64d 100644
--- a/core/modules/book/config/install/core.entity_view_display.node.book.default.yml
+++ b/core/modules/book/config/install/core.entity_view_display.node.book.default.yml
@@ -16,8 +16,10 @@ content:
     label: hidden
     type: text_default
     weight: 100
+    region: content
     settings: {  }
     third_party_settings: {  }
   links:
     weight: 101
+    region: content
 hidden: {  }
diff --git a/core/modules/book/config/install/core.entity_view_display.node.book.teaser.yml b/core/modules/book/config/install/core.entity_view_display.node.book.teaser.yml
index fb22db6..77a62c3 100644
--- a/core/modules/book/config/install/core.entity_view_display.node.book.teaser.yml
+++ b/core/modules/book/config/install/core.entity_view_display.node.book.teaser.yml
@@ -17,9 +17,11 @@ content:
     label: hidden
     type: text_summary_or_trimmed
     weight: 100
+    region: content
     settings:
       trim_length: 600
     third_party_settings: {  }
   links:
     weight: 101
+    region: content
 hidden: {  }
diff --git a/core/modules/content_moderation/src/EntityTypeInfo.php b/core/modules/content_moderation/src/EntityTypeInfo.php
index ab7e918..2c41065 100644
--- a/core/modules/content_moderation/src/EntityTypeInfo.php
+++ b/core/modules/content_moderation/src/EntityTypeInfo.php
@@ -298,9 +298,7 @@ public function entityBaseFieldInfo(EntityTypeInterface $entity_type) {
       ->setClass(ModerationStateFieldItemList::class)
       ->setSetting('target_type', 'moderation_state')
       ->setDisplayOptions('view', [
-        'label' => 'hidden',
-        'type' => 'hidden',
-        'weight' => -5,
+        'region' => 'hidden',
       ])
       ->setDisplayOptions('form', [
         'type' => 'moderation_state_default',
diff --git a/core/modules/field/src/Entity/FieldConfig.php b/core/modules/field/src/Entity/FieldConfig.php
index d7ccc30..1b79b6b 100644
--- a/core/modules/field/src/Entity/FieldConfig.php
+++ b/core/modules/field/src/Entity/FieldConfig.php
@@ -306,7 +306,8 @@ public function isDisplayConfigurable($context) {
    */
   public function getDisplayOptions($display_context) {
     // Hide configurable fields by default.
-    return array('type' => 'hidden');
+    // @todo Remove handling of 'type' in https://www.drupal.org/node/2799641.
+    return array('type' => 'hidden', 'region' => 'hidden');
   }
 
   /**
diff --git a/core/modules/field/tests/src/Kernel/Migrate/d6/MigrateFieldFormatterSettingsTest.php b/core/modules/field/tests/src/Kernel/Migrate/d6/MigrateFieldFormatterSettingsTest.php
index 4c9cbf1..2ee3c77 100644
--- a/core/modules/field/tests/src/Kernel/Migrate/d6/MigrateFieldFormatterSettingsTest.php
+++ b/core/modules/field/tests/src/Kernel/Migrate/d6/MigrateFieldFormatterSettingsTest.php
@@ -32,6 +32,7 @@ public function testEntityDisplaySettings() {
       'type' => 'text_trimmed',
       'settings' => array('trim_length' => 600),
       'third_party_settings' => array(),
+      'region' => 'content',
     );
 
     // Can we load any entity display.
diff --git a/core/modules/field/tests/src/Kernel/Migrate/d6/MigrateFieldWidgetSettingsTest.php b/core/modules/field/tests/src/Kernel/Migrate/d6/MigrateFieldWidgetSettingsTest.php
index a16040f..e7bb636 100644
--- a/core/modules/field/tests/src/Kernel/Migrate/d6/MigrateFieldWidgetSettingsTest.php
+++ b/core/modules/field/tests/src/Kernel/Migrate/d6/MigrateFieldWidgetSettingsTest.php
@@ -33,6 +33,7 @@ public function testWidgetSettings() {
     $expected = array('weight' => 1, 'type' => 'text_textfield');
     $expected['settings'] = array('size' => 60, 'placeholder' => '');
     $expected['third_party_settings'] = array();
+    $expected['region'] = 'content';
     $this->assertIdentical($expected, $component, 'Text field settings are correct.');
 
     // Integer field.
diff --git a/core/modules/field_layout/config/schema/field_layout.schema.yml b/core/modules/field_layout/config/schema/field_layout.schema.yml
new file mode 100644
index 0000000..3460b05
--- /dev/null
+++ b/core/modules/field_layout/config/schema/field_layout.schema.yml
@@ -0,0 +1,13 @@
+core.entity_view_display.*.*.*.third_party.field_layout:
+  type: field_layout.third_party_settings
+
+core.entity_form_display.*.*.*.third_party.field_layout:
+  type: field_layout.third_party_settings
+
+field_layout.third_party_settings:
+  type: mapping
+  label: 'Per-view mode field layout settings'
+  mapping:
+    layout:
+      type: string
+      label: 'Layout'
diff --git a/core/modules/field_layout/css/twocol.layout.css b/core/modules/field_layout/css/twocol.layout.css
new file mode 100644
index 0000000..e969f51
--- /dev/null
+++ b/core/modules/field_layout/css/twocol.layout.css
@@ -0,0 +1,8 @@
+.field-layout-layout--twocol {
+  display: flex;
+  flex-wrap: wrap;
+  justify-content: space-between;
+}
+.field-layout-layout--twocol > .field-layout-region {
+  flex: 0 1 50%;
+}
diff --git a/core/modules/field_layout/field_layout.info.yml b/core/modules/field_layout/field_layout.info.yml
new file mode 100644
index 0000000..62f225a
--- /dev/null
+++ b/core/modules/field_layout/field_layout.info.yml
@@ -0,0 +1,6 @@
+name: 'Field Layout'
+type: module
+description: 'Adds layout capabilities to the Field UI.'
+package: Core (Experimental)
+version: VERSION
+core: 8.x
diff --git a/core/modules/field_layout/field_layout.install b/core/modules/field_layout/field_layout.install
new file mode 100644
index 0000000..c1b1463
--- /dev/null
+++ b/core/modules/field_layout/field_layout.install
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * @file
+ * Contains install and update functions for Field Layout.
+ */
+
+use Drupal\Core\Cache\Cache;
+use Drupal\Core\Entity\Entity\EntityFormDisplay;
+use Drupal\Core\Entity\Entity\EntityViewDisplay;
+
+/**
+ * Implements hook_install().
+ */
+function field_layout_install() {
+  // Save each entity display in order to trigger field_layout_entity_presave().
+  /** @var \Drupal\Core\Entity\Display\EntityDisplayInterface[] $entities */
+  $entities = array_merge(EntityFormDisplay::loadMultiple(), EntityViewDisplay::loadMultiple());
+  foreach ($entities as $entity) {
+    $entity->save();
+  }
+
+  // Invalidate the render cache since all content will now have a layout.
+  Cache::invalidateTags(['rendered']);
+}
diff --git a/core/modules/field_layout/field_layout.libraries.yml b/core/modules/field_layout/field_layout.libraries.yml
new file mode 100644
index 0000000..9798509
--- /dev/null
+++ b/core/modules/field_layout/field_layout.libraries.yml
@@ -0,0 +1,5 @@
+drupal.field_layout.twocol:
+  version: VERSION
+  css:
+    layout:
+      css/twocol.layout.css: {}
diff --git a/core/modules/field_layout/field_layout.module b/core/modules/field_layout/field_layout.module
new file mode 100644
index 0000000..1a696f1
--- /dev/null
+++ b/core/modules/field_layout/field_layout.module
@@ -0,0 +1,150 @@
+<?php
+
+/**
+ * @file
+ * Provides hook implementations for Field Layout.
+ */
+
+use Drupal\Core\Entity\ContentEntityFormInterface;
+use Drupal\Core\Entity\Display\EntityDisplayInterface;
+use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\field_layout\Entity\FieldLayoutEntityFormDisplay;
+use Drupal\field_layout\Entity\FieldLayoutEntityViewDisplay;
+use Drupal\field_layout\Form\FieldLayoutEntityFormDisplayEditForm;
+use Drupal\field_layout\Form\FieldLayoutEntityViewDisplayEditForm;
+
+/**
+ * Implements hook_help().
+ */
+function field_layout_help($route_name, RouteMatchInterface $route_match) {
+  switch ($route_name) {
+    case 'help.page.field_layout':
+      $output = '<h3>' . t('About') . '</h3>';
+      $output .= '<p>' . t('The Field Layout module allows you to arrange fields into regions for content forms and displays.') . '</p>';
+      $output .= '<p>' . t('For more information, see the <a href=":field-layout-documentation">online documentation for the Field Layout module</a>.', [':field-layout-documentation' => 'https://www.drupal.org/documentation/modules/@todo_once_module_name_is_decided_upon']) . '</p>';
+      return $output;
+  }
+}
+
+/**
+ * Implements hook_entity_type_alter().
+ */
+function field_layout_entity_type_alter(array &$entity_types) {
+  /** @var $entity_types \Drupal\Core\Entity\EntityTypeInterface[] */
+  if (\Drupal::moduleHandler()->moduleExists('field_ui')) {
+    $entity_types['entity_view_display']->setClass(FieldLayoutEntityViewDisplay::class);
+    $entity_types['entity_view_display']->setFormClass('edit', FieldLayoutEntityViewDisplayEditForm::class);
+    $entity_types['entity_form_display']->setClass(FieldLayoutEntityFormDisplay::class);
+    $entity_types['entity_form_display']->setFormClass('edit', FieldLayoutEntityFormDisplayEditForm::class);
+  }
+}
+
+/**
+ * Implements hook_entity_presave().
+ */
+function field_layout_entity_presave(EntityInterface $entity) {
+  // Whenever creating a new entity display, set the layout to default.
+  if ($entity instanceof EntityDisplayInterface && !$entity->getThirdPartySettings('field_layout')) {
+    $entity->setThirdPartySetting('field_layout', 'layout', 'default');
+  }
+}
+
+/**
+ * Implements hook_entity_view_alter().
+ */
+function field_layout_entity_view_alter(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display) {
+  _field_layout_apply_layout($build, $display);
+}
+
+/**
+ * Implements hook_form_alter().
+ */
+function field_layout_form_alter(&$form, FormStateInterface $form_state, $form_id) {
+  $form_object = $form_state->getFormObject();
+  if ($form_object instanceof ContentEntityFormInterface) {
+    _field_layout_apply_layout($form, $form_object->getFormDisplay($form_state));
+  }
+}
+
+/**
+ * Applies the layout to an entity build.
+ *
+ * @param array $build
+ *   A renderable array representing the entity content or form.
+ * @param \Drupal\Core\Entity\Display\EntityDisplayInterface $display
+ *   The entity display holding the display options configured for the
+ *   entity components.
+ */
+function _field_layout_apply_layout(array &$build, EntityDisplayInterface $display) {
+  if (!$layout_definition = \Drupal::service('field_layout.layout_repository')->getLayoutForDisplay($display)) {
+    return;
+  }
+
+  // Wrap the regions in a layout element.
+  $build['field_layout']['#theme_wrappers'][] = 'field_layout_layout';
+  $build['field_layout']['#layout'] = $layout_definition['layout'];
+  if (isset($layout_definition['library'])) {
+    $build['#attached']['library'][] = $layout_definition['library'];
+  }
+
+  // Add the regions to the $build in the correct order.
+  foreach ($layout_definition['regions'] as $region => $region_info) {
+    $build['field_layout'][$region]['#theme_wrappers'][] = 'field_layout_region';
+    $build['field_layout'][$region]['#region'] = $region;
+  }
+
+  // Move the field from the top-level of $build into a region-specific section.
+  foreach ($display->getComponents() as $name => $field) {
+    if (isset($build[$name]) && isset($field['region'])) {
+      $build['field_layout'][$field['region']][$name] = $build[$name];
+      unset($build[$name]);
+    }
+  }
+}
+
+/**
+ * Implements hook_theme().
+ */
+function field_layout_theme($existing, $type, $theme, $path) {
+  return [
+    'field_layout_region' => [
+      'render element' => 'elements',
+    ],
+    'field_layout_layout' => [
+      'render element' => 'elements',
+    ],
+  ];
+}
+
+/**
+ * Prepares variables for field layout layout templates.
+ *
+ * Default template: field-layout-layout.html.twig.
+ *
+ * @param array $variables
+ *   An associative array containing:
+ *   - elements: An associative array containing properties of the layout.
+ */
+function template_preprocess_field_layout_layout(&$variables) {
+  // Create the $content variable that templates expect.
+  $variables['content'] = $variables['elements']['#children'];
+  $variables['layout'] = $variables['elements']['#layout'];
+}
+
+/**
+ * Prepares variables for field layout region templates.
+ *
+ * Default template: field-layout-region.html.twig.
+ *
+ * @param array $variables
+ *   An associative array containing:
+ *   - elements: An associative array containing properties of the region.
+ */
+function template_preprocess_field_layout_region(&$variables) {
+  // Create the $content variable that templates expect.
+  $variables['content'] = $variables['elements']['#children'];
+  $variables['region'] = $variables['elements']['#region'];
+}
diff --git a/core/modules/field_layout/field_layout.services.yml b/core/modules/field_layout/field_layout.services.yml
new file mode 100644
index 0000000..b4082ff
--- /dev/null
+++ b/core/modules/field_layout/field_layout.services.yml
@@ -0,0 +1,4 @@
+services:
+  field_layout.layout_repository:
+    class: Drupal\field_layout\LayoutRepository
+    arguments: ['@string_translation']
diff --git a/core/modules/field_layout/src/Entity/FieldLayoutEntityDisplayTrait.php b/core/modules/field_layout/src/Entity/FieldLayoutEntityDisplayTrait.php
new file mode 100644
index 0000000..679d26c
--- /dev/null
+++ b/core/modules/field_layout/src/Entity/FieldLayoutEntityDisplayTrait.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace Drupal\field_layout\Entity;
+
+/**
+ * Provides shared code for entity displays.
+ */
+trait FieldLayoutEntityDisplayTrait {
+
+  /**
+   * Overrides \Drupal\Core\Entity\EntityDisplayBase::getDefaultRegion().
+   */
+  public function getDefaultRegion() {
+    $layout_definition = \Drupal::service('field_layout.layout_repository')->getLayoutForDisplay($this);
+    return key($layout_definition['regions']);
+  }
+
+}
diff --git a/core/modules/field_layout/src/Entity/FieldLayoutEntityFormDisplay.php b/core/modules/field_layout/src/Entity/FieldLayoutEntityFormDisplay.php
new file mode 100644
index 0000000..620d63d
--- /dev/null
+++ b/core/modules/field_layout/src/Entity/FieldLayoutEntityFormDisplay.php
@@ -0,0 +1,14 @@
+<?php
+
+namespace Drupal\field_layout\Entity;
+
+use Drupal\Core\Entity\Entity\EntityFormDisplay;
+
+/**
+ * Provides an entity form display entity that has layout.
+ */
+class FieldLayoutEntityFormDisplay extends EntityFormDisplay {
+
+  use FieldLayoutEntityDisplayTrait;
+
+}
diff --git a/core/modules/field_layout/src/Entity/FieldLayoutEntityViewDisplay.php b/core/modules/field_layout/src/Entity/FieldLayoutEntityViewDisplay.php
new file mode 100644
index 0000000..c4ebabd
--- /dev/null
+++ b/core/modules/field_layout/src/Entity/FieldLayoutEntityViewDisplay.php
@@ -0,0 +1,14 @@
+<?php
+
+namespace Drupal\field_layout\Entity;
+
+use Drupal\Core\Entity\Entity\EntityViewDisplay;
+
+/**
+ * Provides an entity view display entity that has layout.
+ */
+class FieldLayoutEntityViewDisplay extends EntityViewDisplay {
+
+  use FieldLayoutEntityDisplayTrait;
+
+}
diff --git a/core/modules/field_layout/src/Form/FieldLayoutEntityDisplayFormTrait.php b/core/modules/field_layout/src/Form/FieldLayoutEntityDisplayFormTrait.php
new file mode 100644
index 0000000..5d1b2a0
--- /dev/null
+++ b/core/modules/field_layout/src/Form/FieldLayoutEntityDisplayFormTrait.php
@@ -0,0 +1,116 @@
+<?php
+
+namespace Drupal\field_layout\Form;
+
+use Drupal\Core\Entity\Display\EntityDisplayWithLayoutInterface;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Provides shared code for entity display forms.
+ */
+trait FieldLayoutEntityDisplayFormTrait {
+
+  /**
+   * The field layout repository.
+   *
+   * @var \Drupal\field_layout\LayoutRepositoryInterface
+   */
+  protected $fieldLayoutRepository;
+
+  /**
+   * Overrides \Drupal\field_ui\Form\EntityDisplayFormBase::getRegions().
+   */
+  public function getRegions() {
+    $regions = [];
+
+    $layout = $this->fieldLayoutRepository->getLayoutForDisplay($this->getEntity());
+    foreach ($layout['regions'] as $name => $region) {
+      $regions[$name] = [
+        'title' => $region['label'],
+        'message' => $this->t('No field is displayed.')
+      ];
+    }
+
+    $regions['hidden'] = [
+      'title' => $this->t('Disabled', [], ['context' => 'Plural']),
+      'message' => $this->t('No field is hidden.')
+    ];
+
+    return $regions;
+  }
+
+  /**
+   * Adds the field layout section to the form.
+   *
+   * @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 array
+   */
+  protected function addFieldLayout(array $form, FormStateInterface $form_state) {
+    $form['#entity_builders'][] = [static::class, 'updateFieldLayout'];
+
+    $form['field_layouts'] = [
+      '#type' => 'details',
+      '#title' => $this->t('Layout for @bundle in @view_mode', [
+        '@bundle' => str_replace('_', ' ', $this->getEntity()->getTargetBundle()),
+        '@view_mode' => str_replace('_', ' ', $this->getEntity()->getMode()),
+      ]),
+    ];
+    $layout_options = [];
+    foreach ($this->fieldLayoutRepository->getLayoutDefinitions() as $name => $layout) {
+      $layout_options[$name] = $layout['label'];
+    }
+    $form['field_layouts']['field_layout'] = [
+      '#type' => 'select',
+      '#title' => $this->t('Select a layout'),
+      '#options' => $layout_options,
+      '#default_value' => $this->getEntity()->getThirdPartySetting('field_layout', 'layout', 'default'),
+    ];
+
+    return $form;
+  }
+
+  /**
+   * An #entity_builder callback to update the Field Layout settings.
+   *
+   * @param string $entity_type_id
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   * @param array $form
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   */
+  public static function updateFieldLayout($entity_type_id, EntityInterface $entity, &$form, FormStateInterface $form_state) {
+    if ($form_state->isSubmitted() && $entity instanceof EntityDisplayWithLayoutInterface) {
+      $old_layout = $entity->getThirdPartySetting('field_layout', 'layout');
+      $new_layout = $form_state->getValue('field_layout');
+      $entity->setThirdPartySetting('field_layout', 'layout', $new_layout);
+      // If the layout is changing, reset all fields.
+      if ($new_layout !== $old_layout) {
+        // @todo Devise a mechanism for mapping old regions to new ones in
+        //   https://www.drupal.org/node/2796877.
+        $new_region = $entity->getDefaultRegion();
+        foreach ($form_state->getValue('fields') as $field_name => $values) {
+          if (($component = $entity->getComponent($field_name)) && $new_region !== 'hidden') {
+            $component['region'] = $new_region;
+            $entity->setComponent($field_name, $component);
+          }
+          else {
+            $entity->removeComponent($field_name);
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Gets the form entity.
+   *
+   * @return \Drupal\Core\Entity\Display\EntityDisplayInterface
+   *   The current form entity.
+   */
+  abstract public function getEntity();
+
+}
diff --git a/core/modules/field_layout/src/Form/FieldLayoutEntityFormDisplayEditForm.php b/core/modules/field_layout/src/Form/FieldLayoutEntityFormDisplayEditForm.php
new file mode 100644
index 0000000..d57912e
--- /dev/null
+++ b/core/modules/field_layout/src/Form/FieldLayoutEntityFormDisplayEditForm.php
@@ -0,0 +1,54 @@
+<?php
+
+namespace Drupal\field_layout\Form;
+
+use Drupal\Component\Plugin\PluginManagerBase;
+use Drupal\Core\Field\FieldDefinitionInterface;
+use Drupal\Core\Field\FieldTypePluginManagerInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\field_layout\LayoutRepositoryInterface;
+use Drupal\field_ui\Form\EntityFormDisplayEditForm;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Edit form for the EntityFormDisplay entity type.
+ */
+class FieldLayoutEntityFormDisplayEditForm extends EntityFormDisplayEditForm {
+
+  use FieldLayoutEntityDisplayFormTrait;
+
+  /**
+   * FieldLayoutEntityFormDisplayEditForm constructor.
+   *
+   * @param \Drupal\Core\Field\FieldTypePluginManagerInterface $field_type_manager
+   *   The field type manager.
+   * @param \Drupal\Component\Plugin\PluginManagerBase $plugin_manager
+   *   The widget plugin manager.
+   * @param \Drupal\field_layout\LayoutRepositoryInterface $field_layout_repository
+   *   The field layout repository.
+   */
+  public function __construct(FieldTypePluginManagerInterface $field_type_manager, PluginManagerBase $plugin_manager, LayoutRepositoryInterface $field_layout_repository) {
+    parent::__construct($field_type_manager, $plugin_manager);
+    $this->fieldLayoutRepository = $field_layout_repository;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('plugin.manager.field.field_type'),
+      $container->get('plugin.manager.field.widget'),
+      $container->get('field_layout.layout_repository')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function form(array $form, FormStateInterface $form_state) {
+    $form = parent::form($form, $form_state);
+    return $this->addFieldLayout($form, $form_state);
+  }
+
+}
diff --git a/core/modules/field_layout/src/Form/FieldLayoutEntityViewDisplayEditForm.php b/core/modules/field_layout/src/Form/FieldLayoutEntityViewDisplayEditForm.php
new file mode 100644
index 0000000..5c9f26f
--- /dev/null
+++ b/core/modules/field_layout/src/Form/FieldLayoutEntityViewDisplayEditForm.php
@@ -0,0 +1,54 @@
+<?php
+
+namespace Drupal\field_layout\Form;
+
+use Drupal\Component\Plugin\PluginManagerBase;
+use Drupal\Core\Field\FieldDefinitionInterface;
+use Drupal\Core\Field\FieldTypePluginManagerInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\field_layout\LayoutRepositoryInterface;
+use Drupal\field_ui\Form\EntityViewDisplayEditForm;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Edit form for the EntityViewDisplay entity type.
+ */
+class FieldLayoutEntityViewDisplayEditForm extends EntityViewDisplayEditForm {
+
+  use FieldLayoutEntityDisplayFormTrait;
+
+  /**
+   * FieldLayoutEntityViewDisplayEditForm constructor.
+   *
+   * @param \Drupal\Core\Field\FieldTypePluginManagerInterface $field_type_manager
+   *   The field type manager.
+   * @param \Drupal\Component\Plugin\PluginManagerBase $plugin_manager
+   *   The formatter plugin manager.
+   * @param \Drupal\field_layout\LayoutRepositoryInterface $field_layout_repository
+   *   The field layout repository.
+   */
+  public function __construct(FieldTypePluginManagerInterface $field_type_manager, PluginManagerBase $plugin_manager, LayoutRepositoryInterface $field_layout_repository) {
+    parent::__construct($field_type_manager, $plugin_manager);
+    $this->fieldLayoutRepository = $field_layout_repository;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('plugin.manager.field.field_type'),
+      $container->get('plugin.manager.field.formatter'),
+      $container->get('field_layout.layout_repository')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function form(array $form, FormStateInterface $form_state) {
+    $form = parent::form($form, $form_state);
+    return $this->addFieldLayout($form, $form_state);
+  }
+
+}
diff --git a/core/modules/field_layout/src/LayoutRepository.php b/core/modules/field_layout/src/LayoutRepository.php
new file mode 100644
index 0000000..5fe4540
--- /dev/null
+++ b/core/modules/field_layout/src/LayoutRepository.php
@@ -0,0 +1,72 @@
+<?php
+
+namespace Drupal\field_layout;
+
+use Drupal\Core\Entity\Display\EntityDisplayInterface;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\Core\StringTranslation\TranslationInterface;
+
+/**
+ * Provides a repository for field layouts.
+ */
+class LayoutRepository implements LayoutRepositoryInterface {
+
+  use StringTranslationTrait;
+
+  /**
+   * LayoutRepository constructor.
+   *
+   * @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
+   *   The string translation service.
+   */
+  public function __construct(TranslationInterface $string_translation) {
+    $this->stringTranslation = $string_translation;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getLayoutDefinitions() {
+    // @todo Replace with layout_plugin in https://www.drupal.org/node/2296423.
+    $layouts = [
+      'default' => [
+        'label' => $this->t('Default'),
+        'regions' => [
+          'content' => [
+            'label' => $this->t('Content'),
+          ],
+        ],
+      ],
+      'twocol' => [
+        'label' => $this->t('Two column'),
+        'library' => 'field_layout/drupal.field_layout.twocol',
+        'regions' => [
+          'left' => [
+            'label' => $this->t('Left'),
+          ],
+          'right' => [
+            'label' => $this->t('Right'),
+          ],
+        ],
+      ],
+    ];
+    foreach ($layouts as $layout_name => $layout) {
+      $layouts[$layout_name]['layout'] = $layout_name;
+    }
+    return $layouts;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getLayoutForDisplay(EntityDisplayInterface $display) {
+    $layout_name = $display->getThirdPartySetting('field_layout', 'layout', 'default');
+    $layout_definitions = $this->getLayoutDefinitions();
+    if (!$layout_name || !isset($layout_definitions[$layout_name])) {
+      return [];
+    }
+
+    return $layout_definitions[$layout_name];
+  }
+
+}
diff --git a/core/modules/field_layout/src/LayoutRepositoryInterface.php b/core/modules/field_layout/src/LayoutRepositoryInterface.php
new file mode 100644
index 0000000..f8287f3
--- /dev/null
+++ b/core/modules/field_layout/src/LayoutRepositoryInterface.php
@@ -0,0 +1,31 @@
+<?php
+
+namespace Drupal\field_layout;
+
+use Drupal\Core\Entity\Display\EntityDisplayInterface;
+
+/**
+ * Provides the interface for a repository of field layouts.
+ */
+interface LayoutRepositoryInterface {
+
+  /**
+   * Gets all field layout definitions.
+   *
+   * @return array[]
+   *   An array of layout definitions.
+   */
+  public function getLayoutDefinitions();
+
+  /**
+   * Gets the layout definition for the layout used by a display.
+   *
+   * @param \Drupal\Core\Entity\Display\EntityDisplayInterface $display
+   *   The entity display using a layout.
+   *
+   * @return mixed[]
+   *   The layout definition for the given display.
+   */
+  public function getLayoutForDisplay(EntityDisplayInterface $display);
+
+}
diff --git a/core/modules/field_layout/templates/field-layout-layout.html.twig b/core/modules/field_layout/templates/field-layout-layout.html.twig
new file mode 100644
index 0000000..f08168c
--- /dev/null
+++ b/core/modules/field_layout/templates/field-layout-layout.html.twig
@@ -0,0 +1,23 @@
+{#
+/**
+ * @file
+ * Default theme implementation to display a layout.
+ *
+ * Available variables:
+ * - content: The content for this layout, typically blocks.
+ * - attributes: HTML attributes for the layout <div>.
+ * - layout: The name of the layout variable.
+ *
+ * @ingroup themeable
+ */
+#}
+{%
+set classes = [
+'field-layout--' ~ layout|clean_class,
+]
+%}
+{% if content %}
+  <div{{ attributes.addClass(classes) }}>
+    {{ content }}
+  </div>
+{% endif %}
diff --git a/core/modules/field_layout/templates/field-layout-region.html.twig b/core/modules/field_layout/templates/field-layout-region.html.twig
new file mode 100644
index 0000000..11e3604
--- /dev/null
+++ b/core/modules/field_layout/templates/field-layout-region.html.twig
@@ -0,0 +1,24 @@
+{#
+/**
+ * @file
+ * Default theme implementation to display a region.
+ *
+ * Available variables:
+ * - content: The content for this region, typically blocks.
+ * - attributes: HTML attributes for the region <div>.
+ * - region: The name of the region variable.
+ *
+ * @ingroup themeable
+ */
+#}
+{%
+set classes = [
+'field-layout-region',
+'field-layout-region--' ~ region|clean_class,
+]
+%}
+{% if content %}
+  <div{{ attributes.addClass(classes) }}>
+    {{ content }}
+  </div>
+{% endif %}
diff --git a/core/modules/field_layout/tests/modules/field_layout_test/field_layout_test.info.yml b/core/modules/field_layout/tests/modules/field_layout_test/field_layout_test.info.yml
new file mode 100644
index 0000000..4d699e4
--- /dev/null
+++ b/core/modules/field_layout/tests/modules/field_layout_test/field_layout_test.info.yml
@@ -0,0 +1,8 @@
+name: 'Field Layout test'
+type: module
+description: 'Support module for Field Layout tests.'
+core: 8.x
+package: Testing
+version: VERSION
+dependencies:
+  - entity_test
diff --git a/core/modules/field_layout/tests/modules/field_layout_test/field_layout_test.routing.yml b/core/modules/field_layout/tests/modules/field_layout_test/field_layout_test.routing.yml
new file mode 100644
index 0000000..bcea288
--- /dev/null
+++ b/core/modules/field_layout/tests/modules/field_layout_test/field_layout_test.routing.yml
@@ -0,0 +1,7 @@
+entity.entity_test.test_view_mode:
+  path: '/entity_test/{entity_test}/test'
+  defaults:
+    _entity_view: 'entity_test.test'
+    _title: 'Test test view mode'
+  requirements:
+    _entity_access: 'entity_test.view'
diff --git a/core/modules/field_layout/tests/src/FunctionalJavascript/FieldLayoutTest.php b/core/modules/field_layout/tests/src/FunctionalJavascript/FieldLayoutTest.php
new file mode 100644
index 0000000..d6e6d18
--- /dev/null
+++ b/core/modules/field_layout/tests/src/FunctionalJavascript/FieldLayoutTest.php
@@ -0,0 +1,254 @@
+<?php
+
+namespace Drupal\Tests\field_layout\FunctionalJavascript;
+
+use Drupal\entity_test\Entity\EntityTest;
+use Drupal\FunctionalJavascriptTests\JavascriptTestBase;
+
+/**
+ * Tests using field layout for entity displays.
+ *
+ * @group field_layout
+ */
+class FieldLayoutTest extends JavascriptTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['field_layout', 'field_ui', 'node', 'field_layout_test'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->createContentType([
+      'type' => 'article',
+    ]);
+    $this->createNode([
+      'type' => 'article',
+      'title' => 'The node title',
+      'body' => [[
+        'value' => 'The node body',
+      ]],
+    ]);
+    $entity = EntityTest::create([
+      'name' => 'The name for this entity',
+      'field_test_text' => [[
+        'value' => 'The field test text value',
+      ]],
+    ]);
+    $entity->save();
+    $this->drupalLogin($this->drupalCreateUser([
+      'access administration pages',
+      'administer content types',
+      'administer nodes',
+      'administer node fields',
+      'administer node display',
+      'administer node form display',
+      'view test entity',
+      'administer entity_test content',
+      'administer entity_test fields',
+      'administer entity_test display',
+      'administer entity_test form display',
+      'view the administration theme',
+    ]));
+  }
+
+  /**
+   * Tests that layouts are unique per-view mode.
+   */
+  public function testEntityViewModes() {
+    // By default, the field is not visible.
+    $this->drupalGet('entity_test/1/test');
+    $this->assertSession()->elementNotExists('css', '.field-layout-region--content .field--name-field-test-text');
+    $this->drupalGet('entity_test/1');
+    $this->assertSession()->elementNotExists('css', '.field-layout-region--content .field--name-field-test-text');
+
+    // Change the layout for the "test" view mode. See
+    // core.entity_view_mode.entity_test.test.yml.
+    $this->drupalGet('entity_test/structure/entity_test/display');
+    $this->click('#edit-modes');
+    $this->getSession()->getPage()->checkField('display_modes_custom[test]');
+    $this->submitForm([], 'Save');
+    $this->clickLink('configure them');
+    $this->getSession()->getPage()->pressButton('Show row weights');
+    $this->getSession()->getPage()->selectFieldOption('fields[field_test_text][region]', 'content');
+    $this->assertSession()->assertWaitOnAjaxRequest();
+    $this->submitForm([], 'Save');
+
+    // Each view mode has a different layout.
+    $this->drupalGet('entity_test/1/test');
+    $this->assertSession()->elementExists('css', '.field-layout-region--content .field--name-field-test-text');
+    $this->drupalGet('entity_test/1');
+    $this->assertSession()->elementNotExists('css', '.field-layout-region--content .field--name-field-test-text');
+  }
+
+  /**
+   * Tests the use of field layout for entity form displays.
+   */
+  public function testEntityForm() {
+    // By default, the default layout is used.
+    $this->drupalGet('entity_test/manage/1/edit');
+    $this->assertFieldInRegion('field_test_text[0][value]', 'content');
+
+    // The default layout is in use.
+    $this->drupalGet('entity_test/structure/entity_test/form-display');
+    $this->assertEquals(['Content', 'Disabled'], $this->getRegionTitles());
+
+    // Switch the layout to two columns.
+    $this->click('#edit-field-layouts');
+    $this->getSession()->getPage()->selectFieldOption('field_layout', 'twocol');
+    $this->submitForm([], 'Save');
+
+    // The field is moved to the default region for the new layout.
+    $this->assertSession()->pageTextContains('Your settings have been saved.');
+    $this->assertEquals(['Left', 'Right', 'Disabled'], $this->getRegionTitles());
+
+    $this->drupalGet('entity_test/manage/1/edit');
+    // No fields are visible, and the regions don't display when empty.
+    $this->assertFieldInRegion('field_test_text[0][value]', 'left');
+    $this->assertSession()->elementExists('css', '.field-layout-region--left .field--name-field-test-text');
+
+    // After a refresh the new regions are still there.
+    $this->drupalGet('entity_test/structure/entity_test/form-display');
+    $this->assertEquals(['Left', 'Right', 'Disabled'], $this->getRegionTitles());
+
+    // Drag the field to the right region.
+    $field_test_text_row = $this->getSession()->getPage()->find('css', '#field-test-text');
+    $left_region_row = $this->getSession()->getPage()->find('css', '.region-right-message');
+    $field_test_text_row->find('css', '.handle')->dragTo($left_region_row);
+    $this->assertSession()->assertWaitOnAjaxRequest();
+    $field_test_text_row->hasClass('.drag-previous');
+    $this->submitForm([], 'Save');
+    $this->assertSession()->pageTextContains('Your settings have been saved.');
+
+    // The new layout is used.
+    $this->drupalGet('entity_test/manage/1/edit');
+    $this->assertSession()->elementExists('css', '.field-layout-region--right .field--name-field-test-text');
+    $this->assertFieldInRegion('field_test_text[0][value]', 'right');
+
+    // Move the field to the right region without tabledrag.
+    $this->drupalGet('entity_test/structure/entity_test/form-display');
+    $this->getSession()->getPage()->pressButton('Show row weights');
+    $this->getSession()->getPage()->selectFieldOption('fields[field_test_text][region]', 'right');
+    $this->assertSession()->assertWaitOnAjaxRequest();
+    $this->submitForm([], 'Save');
+    $this->assertSession()->pageTextContains('Your settings have been saved.');
+
+    // The updated region is used.
+    $this->drupalGet('entity_test/manage/1/edit');
+    $this->assertFieldInRegion('field_test_text[0][value]', 'right');
+
+    // The layout is still in use without Field UI.
+    $this->container->get('module_installer')->uninstall(['field_ui']);
+    $this->drupalGet('entity_test/manage/1/edit');
+    $this->assertFieldInRegion('field_test_text[0][value]', 'right');
+  }
+
+  /**
+   * Tests the use of field layout for entity view displays.
+   */
+  public function testEntityView() {
+    // The default layout is in use.
+    $this->drupalGet('entity_test/structure/entity_test/display');
+    $this->assertEquals(['Content', 'Disabled'], $this->getRegionTitles());
+
+    // Switch the layout to two columns.
+    $this->click('#edit-field-layouts');
+    $this->getSession()->getPage()->selectFieldOption('field_layout', 'twocol');
+    $this->submitForm([], 'Save');
+
+    $this->assertSession()->pageTextContains('Your settings have been saved.');
+    $this->assertEquals(['Left', 'Right', 'Disabled'], $this->getRegionTitles());
+
+    $this->drupalGet('entity_test/1');
+    // No fields are visible, and the regions don't display when empty.
+    $this->assertSession()->elementNotExists('css', '.field-layout--default');
+    $this->assertSession()->elementNotExists('css', '.field-layout-region');
+    $this->assertSession()->elementNotExists('css', '.field--name-field-test-text');
+
+    // After a refresh the new regions are still there.
+    $this->drupalGet('entity_test/structure/entity_test/display');
+    $this->assertEquals(['Left', 'Right', 'Disabled'], $this->getRegionTitles());
+
+    // Drag the field to the left region.
+    $this->assertTrue($this->assertSession()->optionExists('fields[field_test_text][type]', 'hidden')->isSelected());
+    $field_test_text_row = $this->getSession()->getPage()->find('css', '#field-test-text');
+    $left_region_row = $this->getSession()->getPage()->find('css', '.region-left-message');
+    $field_test_text_row->find('css', '.handle')->dragTo($left_region_row);
+    $this->assertSession()->assertWaitOnAjaxRequest();
+    $field_test_text_row->hasClass('.drag-previous');
+    $this->assertTrue($this->assertSession()->optionExists('fields[field_test_text][type]', 'text_trimmed')->isSelected());
+    $this->submitForm([], 'Save');
+    $this->assertSession()->pageTextContains('Your settings have been saved.');
+
+    // The new layout is used.
+    $this->drupalGet('entity_test/1');
+    $this->assertSession()->elementExists('css', '.field-layout--twocol');
+    $this->assertSession()->elementExists('css', '.field-layout-region--left .field--name-field-test-text');
+
+    // Move the field to the right region without tabledrag.
+    $this->drupalGet('entity_test/structure/entity_test/display');
+    $this->getSession()->getPage()->pressButton('Show row weights');
+    $this->getSession()->getPage()->selectFieldOption('fields[field_test_text][region]', 'right');
+    $this->assertSession()->assertWaitOnAjaxRequest();
+    $this->submitForm([], 'Save');
+    $this->assertSession()->pageTextContains('Your settings have been saved.');
+
+    // The updated region is used.
+    $this->drupalGet('entity_test/1');
+    $this->assertSession()->elementExists('css', '.field-layout-region--right .field--name-field-test-text');
+
+    // The layout is still in use without Field UI.
+    $this->container->get('module_installer')->uninstall(['field_ui']);
+    $this->drupalGet('entity_test/1');
+    $this->assertSession()->elementExists('css', '.field-layout--twocol');
+    $this->assertSession()->elementExists('css', '.field-layout-region--right .field--name-field-test-text');
+  }
+
+  /**
+   * Tests an entity type that has fields shown by default.
+   */
+  public function testNodeView() {
+    // By default, the default layout is used.
+    $this->drupalGet('node/1');
+    $this->assertSession()->elementExists('css', '.field-layout--default');
+    $this->assertSession()->elementExists('css', '.field-layout-region--content .field--name-body');
+
+    $this->drupalGet('admin/structure/types/manage/article/display');
+    $this->assertEquals(['Content', 'Disabled'], $this->getRegionTitles());
+    $this->assertSession()->optionExists('fields[body][region]', 'content');
+  }
+
+  /**
+   * Gets the region titles on the page.
+   *
+   * @return string[]
+   *   An array of region titles.
+   */
+  protected function getRegionTitles() {
+    $region_titles = [];
+    $region_title_elements = $this->getSession()->getPage()->findAll('css', '.region-title td');
+    /** @var \Behat\Mink\Element\NodeElement[] $region_title_elements */
+    foreach ($region_title_elements as $region_title_element) {
+      $region_titles[] = $region_title_element->getText();
+    }
+    return $region_titles;
+  }
+
+  /**
+   * Asserts that a field exists in a given region.
+   *
+   * @param string $field_selector
+   *   The field selector, one of field id|name|label|value.
+   * @param string $region_name
+   *   The machine name of the region.
+   */
+  protected function assertFieldInRegion($field_selector, $region_name) {
+    $region_element = $this->getSession()->getPage()->find('css', ".field-layout-region--$region_name");
+    $this->assertSession()->fieldExists($field_selector, $region_element);
+  }
+
+}
diff --git a/core/modules/field_layout/tests/src/Unit/LayoutRepositoryTest.php b/core/modules/field_layout/tests/src/Unit/LayoutRepositoryTest.php
new file mode 100644
index 0000000..8bbde70
--- /dev/null
+++ b/core/modules/field_layout/tests/src/Unit/LayoutRepositoryTest.php
@@ -0,0 +1,61 @@
+<?php
+
+namespace Drupal\Tests\field_layout\Unit;
+
+use Drupal\Core\Entity\Display\EntityDisplayInterface;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Drupal\field_layout\LayoutRepository;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * @coversDefaultClass \Drupal\field_layout\LayoutRepository
+ * @group field_layout
+ */
+class LayoutRepositoryTest extends UnitTestCase {
+
+  /**
+   * @covers ::getLayoutDefinitions
+   */
+  public function testGetLayoutDefinitions() {
+    $layout_repository = new LayoutRepository($this->getStringTranslationStub());
+
+    $expected = ['default', 'twocol'];
+
+    $layout_definitions = $layout_repository->getLayoutDefinitions();
+    $this->assertEquals($expected, array_keys($layout_definitions));
+  }
+
+  /**
+   * @covers ::getLayoutForDisplay
+   */
+  public function testGetLayoutForDisplay() {
+    $layout_repository = new LayoutRepository($this->getStringTranslationStub());
+
+    $display = $this->prophesize(EntityDisplayInterface::class);
+    $display->getThirdPartySetting('field_layout', 'layout', 'default')->willReturn('twocol');
+
+    $expected = ['left', 'right'];
+
+    $layout_definition = $layout_repository->getLayoutForDisplay($display->reveal());
+    $this->assertSame('twocol', $layout_definition['layout']);
+    $this->assertEquals($expected, array_keys($layout_definition['regions']));
+  }
+
+  /**
+   * @covers ::getLayoutForDisplay
+   */
+  public function testGetLayoutForDisplayEmpty() {
+    $layout_repository = new LayoutRepository($this->getStringTranslationStub());
+
+    $display = $this->prophesize(EntityDisplayInterface::class);
+    $display->getThirdPartySetting('field_layout', 'layout', 'default')->willReturn('default');
+
+    $expected = ['content'];
+
+    $layout_definition = $layout_repository->getLayoutForDisplay($display->reveal());
+    $this->assertSame('default', $layout_definition['layout']);
+    $this->assertEquals($expected, array_keys($layout_definition['regions']));
+  }
+
+}
diff --git a/core/modules/field_ui/field_ui.js b/core/modules/field_ui/field_ui.js
index a53553d..a486ffc 100644
--- a/core/modules/field_ui/field_ui.js
+++ b/core/modules/field_ui/field_ui.js
@@ -270,6 +270,10 @@ else if ($this.is('.region-empty')) {
     this.$pluginSelect = $(row).find('select.field-plugin-type');
     this.$pluginSelect.on('change', Drupal.fieldUIOverview.onChange);
 
+    // Attach change listener to the 'region' select.
+    this.$regionSelect = $(row).find('select.field-region');
+    this.$regionSelect.on('change', Drupal.fieldUIOverview.onChange);
+
     return this;
   };
 
@@ -282,7 +286,7 @@ else if ($this.is('.region-empty')) {
      *   Either 'hidden' or 'content'.
      */
     getRegion: function () {
-      return (this.$pluginSelect.val() === 'hidden') ? 'hidden' : 'content';
+      return this.$regionSelect.val();
     },
 
     /**
@@ -305,14 +309,17 @@ else if ($this.is('.region-empty')) {
      *   {@link Drupal.fieldUIOverview.AJAXRefreshRows}.
      */
     regionChange: function (region) {
+      // Replace dashes with underscores.
+      region = region.replace(/-/g, '_');
+
+      // Set the region of the select list.
+      this.$regionSelect.val(region);
 
       // When triggered by a row drag, the 'format' select needs to be adjusted
       // to the new region.
       var currentValue = this.$pluginSelect.val();
       var value;
-      // @TODO Check if this couldn't just be like
-      // if (region !== 'hidden') {
-      if (region === 'content') {
+      if (region !== 'hidden') {
         if (currentValue === 'hidden') {
           // Restore the formatter back to the default formatter. Pseudo-fields
           // do not have default formatters, we just return to 'visible' for
diff --git a/core/modules/field_ui/src/Form/EntityDisplayFormBase.php b/core/modules/field_ui/src/Form/EntityDisplayFormBase.php
index 586f1ef..553c034 100644
--- a/core/modules/field_ui/src/Form/EntityDisplayFormBase.php
+++ b/core/modules/field_ui/src/Form/EntityDisplayFormBase.php
@@ -4,6 +4,7 @@
 
 use Drupal\Component\Plugin\Factory\DefaultFactory;
 use Drupal\Component\Plugin\PluginManagerBase;
+use Drupal\Core\Entity\Display\EntityDisplayInterface;
 use Drupal\Core\Entity\EntityForm;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityWithPluginCollectionInterface;
@@ -172,6 +173,13 @@ public function form(array $form, FormStateInterface $form_state) {
           'subgroup' => 'field-parent',
           'source' => 'field-name',
         ),
+        array(
+          'action' => 'match',
+          'relationship' => 'parent',
+          'group' => 'field-region',
+          'subgroup' => 'field-region',
+          'source' => 'field-name',
+        ),
       ),
     );
 
@@ -309,6 +317,15 @@ protected function buildFieldRow(FieldDefinitionInterface $field_definition, arr
           '#attributes' => array('class' => array('field-name')),
         ),
       ),
+      'region' => array(
+        '#type' => 'select',
+        '#title' => $this->t('Region for @title', array('@title' => $label)),
+        '#title_display' => 'invisible',
+        '#options' => $this->getRegionOptions(),
+        '#empty_value' => 'hidden',
+        '#default_value' => isset($display_options['region']) ? $display_options['region'] : 'hidden',
+        '#attributes' => array('class' => array('field-region')),
+      ),
     );
 
     $field_row['plugin'] = array(
@@ -474,6 +491,15 @@ protected function buildExtraFieldRow($field_id, $extra_field) {
           '#attributes' => array('class' => array('field-name')),
         ),
       ),
+      'region' => array(
+        '#type' => 'select',
+        '#title' => $this->t('Region for @title', array('@title' => $extra_field['label'])),
+        '#title_display' => 'invisible',
+        '#options' => $this->getRegionOptions(),
+        '#empty_value' => 'hidden',
+        '#default_value' => $display_options ? $display_options['region'] : 'hidden',
+        '#attributes' => array('class' => array('field-region')),
+      ),
       'plugin' => array(
         'type' => array(
           '#type' => 'select',
@@ -548,44 +574,118 @@ protected function copyFormValuesToEntity(EntityInterface $entity, array $form,
 
     // Collect data for 'regular' fields.
     foreach ($form['#fields'] as $field_name) {
-      $values = $form_values['fields'][$field_name];
+      $this->processFieldUpdates($field_name, $form_values['fields'][$field_name], $entity, $form_state);
+    }
+
+    // Collect data for 'extra' fields.
+    foreach ($form['#extra'] as $name) {
+      $this->processFieldUpdates($name, $form_values['fields'][$name], $entity, $form_state);
+    }
 
-      if ($values['type'] == 'hidden') {
-        $entity->removeComponent($field_name);
+    $form_state->set('entity_display_components_updated', TRUE);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function save(array $form, FormStateInterface $form_state) {
+    $form_state->set('entity_display_components_updated', NULL);
+    return parent::save($form, $form_state);
+  }
+
+  /**
+   * Processes updates to the components for a given field.
+   *
+   * @param string $field_name
+   *   The field name being processed.
+   * @param array $values
+   *   The submitted form values.
+   * @param \Drupal\Core\Entity\Display\EntityDisplayInterface $entity
+   *   The entity being updated.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   */
+  protected function processFieldUpdates($field_name, array $values, EntityDisplayInterface $entity, FormStateInterface $form_state) {
+    $options = $entity->getComponent($field_name) ?: [];
+    // If the components have been updated, use the values from the entity.
+    // @todo In https://www.drupal.org/node/2799641, replace this with:
+    //   $remove_component = $values['region'] === 'hidden';
+    if ($form_state->get('entity_display_components_updated')) {
+      $options += ['region' => 'hidden'];
+      $remove_component = $options['region'] === 'hidden';
+    }
+    else {
+      $remove_component = $this->determineComponentAction($options, $values);
+    }
+
+    if ($remove_component) {
+      $entity->removeComponent($field_name);
+    }
+    else {
+      // Update field settings only if the submit handler told us to.
+      if ($form_state->get('plugin_settings_update') === $field_name) {
+        // Only store settings actually used by the selected plugin.
+        $default_settings = $this->pluginManager->getDefaultSettings($options['type']);
+        $options['settings'] = isset($values['settings_edit_form']['settings']) ? array_intersect_key($values['settings_edit_form']['settings'], $default_settings) : [];
+        $options['third_party_settings'] = isset($values['settings_edit_form']['third_party_settings']) ? $values['settings_edit_form']['third_party_settings'] : [];
+        $form_state->set('plugin_settings_update', NULL);
       }
-      else {
-        $options = $entity->getComponent($field_name);
-
-        // Update field settings only if the submit handler told us to.
-        if ($form_state->get('plugin_settings_update') === $field_name) {
-          // Only store settings actually used by the selected plugin.
-          $default_settings = $this->pluginManager->getDefaultSettings($options['type']);
-          $options['settings'] = isset($values['settings_edit_form']['settings']) ? array_intersect_key($values['settings_edit_form']['settings'], $default_settings) : [];
-          $options['third_party_settings'] = isset($values['settings_edit_form']['third_party_settings']) ? $values['settings_edit_form']['third_party_settings'] : [];
-          $form_state->set('plugin_settings_update', NULL);
-        }
 
+      if (isset($values['type'])) {
         $options['type'] = $values['type'];
-        $options['weight'] = $values['weight'];
-        // Only formatters have configurable label visibility.
-        if (isset($values['label'])) {
-          $options['label'] = $values['label'];
-        }
-        $entity->setComponent($field_name, $options);
       }
+      $options['weight'] = $values['weight'];
+      if (isset($values['region'])) {
+        $options['region'] = $values['region'];
+      }
+      // Only formatters have configurable label visibility.
+      if (isset($values['label'])) {
+        $options['label'] = $values['label'];
+      }
+      $entity->setComponent($field_name, $options);
     }
+  }
 
-    // Collect data for 'extra' fields.
-    foreach ($form['#extra'] as $name) {
-      if ($form_values['fields'][$name]['type'] == 'hidden') {
-        $entity->removeComponent($name);
+  /**
+   * Determines whether a component should be updated or removed.
+   *
+   * @todo Remove handling of 'type' in https://www.drupal.org/node/2799641.
+   *
+   * @param array $old_values
+   *   An array of the old values for a given component.
+   * @param array $new_values
+   *   An array of the new values for a given component.
+   *
+   * @return bool
+   *   TRUE if the component should be removed, FALSE if it should be updated.
+   */
+  protected function determineComponentAction(array &$old_values, array &$new_values) {
+    $has_type_change = $new_values['type'] !== (isset($old_values['type']) ? $old_values['type'] : 'hidden');
+    $has_region_change = $new_values['region'] !== (isset($old_values['region']) ? $old_values['region'] : 'hidden');
+    // If the type and region both changed or neither changed, the action will
+    // be the same. Base the decision on whether the region is hidden.
+    if ($has_type_change === $has_region_change) {
+      $remove_component = $new_values['region'] === 'hidden';
+    }
+    else {
+      if ($has_region_change) {
+        // If only the region changed, remove the component if it is now hidden.
+        $remove_component = $new_values['region'] === 'hidden';
+        // If the region and type mismatch, remove the invalid type.
+        if ($new_values['region'] !== 'hidden' && $new_values['type'] === 'hidden') {
+          unset($new_values['type'], $old_values['type']);
+        }
       }
       else {
-        $entity->setComponent($name, array(
-          'weight' => $form_values['fields'][$name]['weight'],
-        ));
+        // If only the type changed, remove the component if it is now hidden.
+        $remove_component = $new_values['type'] === 'hidden';
+        // If the region and type mismatch, remove the invalid region.
+        if ($new_values['region'] === 'hidden' && $new_values['type'] !== 'hidden') {
+          unset($new_values['region'], $old_values['region']);
+        }
       }
     }
+    return $remove_component;
   }
 
   /**
@@ -813,7 +913,7 @@ public function getRowRegion($row) {
     switch ($row['#row_type']) {
       case 'field':
       case 'extra_field':
-        return ($row['plugin']['type']['#value'] == 'hidden' ? 'hidden' : 'content');
+        return $row['region']['#value'] ?: 'hidden';
     }
   }
 
diff --git a/core/modules/field_ui/src/Form/EntityFormDisplayEditForm.php b/core/modules/field_ui/src/Form/EntityFormDisplayEditForm.php
index 741b98d..af8e2ed 100644
--- a/core/modules/field_ui/src/Form/EntityFormDisplayEditForm.php
+++ b/core/modules/field_ui/src/Form/EntityFormDisplayEditForm.php
@@ -94,6 +94,7 @@ protected function getTableHeader() {
       $this->t('Field'),
       $this->t('Weight'),
       $this->t('Parent'),
+      $this->t('Region'),
       array('data' => $this->t('Widget'), 'colspan' => 3),
     );
   }
diff --git a/core/modules/field_ui/src/Form/EntityViewDisplayEditForm.php b/core/modules/field_ui/src/Form/EntityViewDisplayEditForm.php
index f273325..174726f 100644
--- a/core/modules/field_ui/src/Form/EntityViewDisplayEditForm.php
+++ b/core/modules/field_ui/src/Form/EntityViewDisplayEditForm.php
@@ -127,6 +127,7 @@ protected function getTableHeader() {
       $this->t('Field'),
       $this->t('Weight'),
       $this->t('Parent'),
+      $this->t('Region'),
       $this->t('Label'),
       array('data' => $this->t('Format'), 'colspan' => 3),
     );
diff --git a/core/modules/field_ui/tests/src/Kernel/EntityDisplayTest.php b/core/modules/field_ui/tests/src/Kernel/EntityDisplayTest.php
index be188db..2176fcc 100644
--- a/core/modules/field_ui/tests/src/Kernel/EntityDisplayTest.php
+++ b/core/modules/field_ui/tests/src/Kernel/EntityDisplayTest.php
@@ -53,15 +53,15 @@ public function testEntityDisplayCRUD() {
     // Check that providing no 'weight' results in the highest current weight
     // being assigned. The 'name' field's formatter has weight -5, therefore
     // these follow.
-    $expected['component_1'] = array('weight' => -4, 'settings' => array(), 'third_party_settings' => array());
-    $expected['component_2'] = array('weight' => -3, 'settings' => array(), 'third_party_settings' => array());
+    $expected['component_1'] = array('weight' => -4, 'settings' => array(), 'third_party_settings' => array(), 'region' => 'content');
+    $expected['component_2'] = array('weight' => -3, 'settings' => array(), 'third_party_settings' => array(), 'region' => 'content');
     $display->setComponent('component_1');
     $display->setComponent('component_2');
     $this->assertEqual($display->getComponent('component_1'), $expected['component_1']);
     $this->assertEqual($display->getComponent('component_2'), $expected['component_2']);
 
     // Check that arbitrary options are correctly stored.
-    $expected['component_3'] = array('weight' => 10, 'third_party_settings' => array('field_test' => array('foo' => 'bar')), 'settings' => array());
+    $expected['component_3'] = array('weight' => 10, 'third_party_settings' => array('field_test' => array('foo' => 'bar')), 'settings' => array(), 'region' => 'content');
     $display->setComponent('component_3', $expected['component_3']);
     $this->assertEqual($display->getComponent('component_3'), $expected['component_3']);
 
@@ -86,6 +86,7 @@ public function testEntityDisplayCRUD() {
         'link_to_entity' => FALSE,
       ),
       'third_party_settings' => array(),
+      'region' => 'content',
     );
     $this->assertEqual($display->getComponents(), $expected);
 
@@ -148,7 +149,7 @@ public function testEntityGetDisplay() {
     $display = entity_get_display('entity_test', 'entity_test', 'default');
     $this->assertFalse($display->isNew());
     $this->assertEqual($display->id(), 'entity_test.entity_test.default');
-    $this->assertEqual($display->getComponent('component_1'), array( 'weight' => 10, 'settings' => array(), 'third_party_settings' => array()));
+    $this->assertEqual($display->getComponent('component_1'), array( 'weight' => 10, 'settings' => array(), 'third_party_settings' => array(), 'region' => 'content'));
   }
 
   /**
@@ -164,14 +165,14 @@ public function testExtraFieldComponent() {
 
     // Check that the default visibility taken into account for extra fields
     // unknown in the display.
-    $this->assertEqual($display->getComponent('display_extra_field'), array('weight' => 5));
+    $this->assertEqual($display->getComponent('display_extra_field'), array('weight' => 5, 'region' => 'content'));
     $this->assertNull($display->getComponent('display_extra_field_hidden'));
 
     // Check that setting explicit options overrides the defaults.
     $display->removeComponent('display_extra_field');
     $display->setComponent('display_extra_field_hidden', array('weight' => 10));
     $this->assertNull($display->getComponent('display_extra_field'));
-    $this->assertEqual($display->getComponent('display_extra_field_hidden'), array('weight' => 10, 'settings' => array(), 'third_party_settings' => array()));
+    $this->assertEqual($display->getComponent('display_extra_field_hidden'), array('weight' => 10, 'settings' => array(), 'third_party_settings' => array(), 'region' => 'content'));
   }
 
   /**
@@ -209,6 +210,7 @@ public function testFieldComponent() {
       'type' => $default_formatter,
       'settings' => $formatter_settings,
       'third_party_settings' => array(),
+      'region' => 'content',
     );
     $this->assertEqual($display->getComponent($field_name), $expected);
 
@@ -258,6 +260,7 @@ public function testBaseFieldComponent() {
         'settings' => $formatter_settings,
         'third_party_settings' => array(),
         'weight' => 10,
+        'region' => 'content',
       ),
       'test_display_non_configurable' => array(
         'label' => 'above',
@@ -265,6 +268,7 @@ public function testBaseFieldComponent() {
         'settings' => $formatter_settings,
         'third_party_settings' => array(),
         'weight' => 11,
+        'region' => 'content',
       ),
     );
     foreach ($expected as $field_name => $options) {
diff --git a/core/modules/field_ui/tests/src/Kernel/EntityFormDisplayTest.php b/core/modules/field_ui/tests/src/Kernel/EntityFormDisplayTest.php
index e38db7c..d1a8458 100644
--- a/core/modules/field_ui/tests/src/Kernel/EntityFormDisplayTest.php
+++ b/core/modules/field_ui/tests/src/Kernel/EntityFormDisplayTest.php
@@ -43,7 +43,13 @@ public function testEntityGetFromDisplay() {
     $form_display = entity_get_form_display('entity_test', 'entity_test', 'default');
     $this->assertFalse($form_display->isNew());
     $this->assertEqual($form_display->id(), 'entity_test.entity_test.default');
-    $this->assertEqual($form_display->getComponent('component_1'), array('weight' => 10, 'settings' => array(), 'third_party_settings' => array()));
+    $expected = [
+      'weight' => 10,
+      'settings' => [],
+      'third_party_settings' => [],
+      'region' => 'content',
+    ];
+    $this->assertEquals($expected, $form_display->getComponent('component_1'));
   }
 
   /**
@@ -80,6 +86,7 @@ public function testFieldComponent() {
       'type' => $default_widget,
       'settings' => $widget_settings,
       'third_party_settings' => array(),
+      'region' => 'content',
     );
     $this->assertEqual($form_display->getComponent($field_name), $expected);
 
@@ -134,12 +141,14 @@ public function testBaseFieldComponent() {
         'settings' => $formatter_settings,
         'third_party_settings' => array(),
         'weight' => 10,
+        'region' => 'content',
       ),
       'test_display_non_configurable' => array(
         'type' => 'text_textfield',
         'settings' => $formatter_settings,
         'third_party_settings' => array(),
         'weight' => 11,
+        'region' => 'content',
       ),
     );
     foreach ($expected as $field_name => $options) {
diff --git a/core/modules/forum/config/optional/core.entity_form_display.comment.comment_forum.default.yml b/core/modules/forum/config/optional/core.entity_form_display.comment.comment_forum.default.yml
index a09c30b..4738bbb 100644
--- a/core/modules/forum/config/optional/core.entity_form_display.comment.comment_forum.default.yml
+++ b/core/modules/forum/config/optional/core.entity_form_display.comment.comment_forum.default.yml
@@ -13,9 +13,11 @@ mode: default
 content:
   author:
     weight: -2
+    region: content
   comment_body:
     type: text_textarea
     weight: 11
+    region: content
     settings:
       rows: 5
       placeholder: ''
@@ -23,6 +25,7 @@ content:
   subject:
     type: string_textfield
     weight: 10
+    region: content
     settings:
       size: 60
       placeholder: ''
diff --git a/core/modules/forum/config/optional/core.entity_form_display.node.forum.default.yml b/core/modules/forum/config/optional/core.entity_form_display.node.forum.default.yml
index c66ba23..6773d32 100644
--- a/core/modules/forum/config/optional/core.entity_form_display.node.forum.default.yml
+++ b/core/modules/forum/config/optional/core.entity_form_display.node.forum.default.yml
@@ -17,6 +17,7 @@ content:
   body:
     type: text_textarea_with_summary
     weight: 27
+    region: content
     settings:
       rows: 9
       summary_rows: 3
@@ -25,11 +26,13 @@ content:
   comment_forum:
     type: comment_default
     weight: 20
+    region: content
     settings: {  }
     third_party_settings: {  }
   created:
     type: datetime_timestamp
     weight: 10
+    region: content
     settings: {  }
     third_party_settings: {  }
   promote:
@@ -37,21 +40,25 @@ content:
     settings:
       display_label: true
     weight: 15
+    region: content
     third_party_settings: {  }
   sticky:
     type: boolean_checkbox
     settings:
       display_label: true
     weight: 16
+    region: content
     third_party_settings: {  }
   taxonomy_forums:
     type: options_select
     weight: 26
+    region: content
     settings: {  }
     third_party_settings: {  }
   title:
     type: string_textfield
     weight: -5
+    region: content
     settings:
       size: 60
       placeholder: ''
@@ -59,6 +66,7 @@ content:
   uid:
     type: entity_reference_autocomplete
     weight: 5
+    region: content
     settings:
       match_operator: CONTAINS
       size: 60
diff --git a/core/modules/forum/config/optional/core.entity_form_display.taxonomy_term.forums.default.yml b/core/modules/forum/config/optional/core.entity_form_display.taxonomy_term.forums.default.yml
index b18c869..50df98a 100644
--- a/core/modules/forum/config/optional/core.entity_form_display.taxonomy_term.forums.default.yml
+++ b/core/modules/forum/config/optional/core.entity_form_display.taxonomy_term.forums.default.yml
@@ -14,11 +14,13 @@ content:
   description:
     type: text_textfield
     weight: 0
+    region: content
     settings: {  }
     third_party_settings: {  }
   name:
     type: string_textfield
     weight: -5
+    region: content
     settings:
       size: 60
       placeholder: ''
diff --git a/core/modules/forum/config/optional/core.entity_view_display.comment.comment_forum.default.yml b/core/modules/forum/config/optional/core.entity_view_display.comment.comment_forum.default.yml
index f4f0112..befeba8 100644
--- a/core/modules/forum/config/optional/core.entity_view_display.comment.comment_forum.default.yml
+++ b/core/modules/forum/config/optional/core.entity_view_display.comment.comment_forum.default.yml
@@ -15,8 +15,10 @@ content:
     label: hidden
     type: text_default
     weight: 0
+    region: content
     settings: {  }
     third_party_settings: {  }
   links:
     weight: 100
+    region: content
 hidden: {  }
diff --git a/core/modules/forum/config/optional/core.entity_view_display.node.forum.default.yml b/core/modules/forum/config/optional/core.entity_view_display.node.forum.default.yml
index b157c83..f3e8c5c 100644
--- a/core/modules/forum/config/optional/core.entity_view_display.node.forum.default.yml
+++ b/core/modules/forum/config/optional/core.entity_view_display.node.forum.default.yml
@@ -20,21 +20,25 @@ content:
     label: hidden
     type: text_default
     weight: 0
+    region: content
     settings: {  }
     third_party_settings: {  }
   comment_forum:
     label: hidden
     type: comment_default
     weight: 20
+    region: content
     settings:
       view_mode: default
       pager_id: 0
     third_party_settings: {  }
   links:
     weight: 100
+    region: content
   taxonomy_forums:
     type: entity_reference_label
     weight: -1
+    region: content
     label: above
     settings:
       link: true
diff --git a/core/modules/forum/config/optional/core.entity_view_display.node.forum.teaser.yml b/core/modules/forum/config/optional/core.entity_view_display.node.forum.teaser.yml
index 4405e71..7b174f4 100644
--- a/core/modules/forum/config/optional/core.entity_view_display.node.forum.teaser.yml
+++ b/core/modules/forum/config/optional/core.entity_view_display.node.forum.teaser.yml
@@ -19,14 +19,17 @@ content:
     label: hidden
     type: text_summary_or_trimmed
     weight: 100
+    region: content
     settings:
       trim_length: 600
     third_party_settings: {  }
   links:
     weight: 101
+    region: content
   taxonomy_forums:
     type: entity_reference_label
     weight: 10
+    region: content
     label: above
     settings:
       link: true
diff --git a/core/modules/forum/config/optional/core.entity_view_display.taxonomy_term.forums.default.yml b/core/modules/forum/config/optional/core.entity_view_display.taxonomy_term.forums.default.yml
index d1242d9..b326039 100644
--- a/core/modules/forum/config/optional/core.entity_view_display.taxonomy_term.forums.default.yml
+++ b/core/modules/forum/config/optional/core.entity_view_display.taxonomy_term.forums.default.yml
@@ -14,6 +14,7 @@ content:
   description:
     type: text_default
     weight: 0
+    region: content
     settings: {  }
     third_party_settings: {  }
     label: above
diff --git a/core/modules/options/tests/options_config_install_test/config/install/core.entity_form_display.node.options_install_test.default.yml b/core/modules/options/tests/options_config_install_test/config/install/core.entity_form_display.node.options_install_test.default.yml
index 19a2ef7..ff5f0ec 100644
--- a/core/modules/options/tests/options_config_install_test/config/install/core.entity_form_display.node.options_install_test.default.yml
+++ b/core/modules/options/tests/options_config_install_test/config/install/core.entity_form_display.node.options_install_test.default.yml
@@ -14,6 +14,7 @@ content:
   title:
     type: string_textfield
     weight: -5
+    region: content
     settings:
       size: 60
       placeholder: ''
@@ -21,6 +22,7 @@ content:
   uid:
     type: entity_reference_autocomplete
     weight: 5
+    region: content
     settings:
       match_operator: CONTAINS
       size: 60
@@ -29,6 +31,7 @@ content:
   created:
     type: datetime_timestamp
     weight: 10
+    region: content
     settings: {  }
     third_party_settings: {  }
   promote:
@@ -36,16 +39,19 @@ content:
     settings:
       display_label: true
     weight: 15
+    region: content
     third_party_settings: {  }
   sticky:
     type: boolean_checkbox
     settings:
       display_label: true
     weight: 16
+    region: content
     third_party_settings: {  }
   body:
     type: text_textarea_with_summary
     weight: 26
+    region: content
     settings:
       rows: 9
       summary_rows: 3
diff --git a/core/modules/options/tests/options_config_install_test/config/install/core.entity_view_display.node.options_install_test.default.yml b/core/modules/options/tests/options_config_install_test/config/install/core.entity_view_display.node.options_install_test.default.yml
index c107b10..aaea1cb 100644
--- a/core/modules/options/tests/options_config_install_test/config/install/core.entity_view_display.node.options_install_test.default.yml
+++ b/core/modules/options/tests/options_config_install_test/config/install/core.entity_view_display.node.options_install_test.default.yml
@@ -14,10 +14,12 @@ mode: default
 content:
   links:
     weight: 100
+    region: content
   body:
     label: hidden
     type: text_default
     weight: 101
+    region: content
     settings: {  }
     third_party_settings: {  }
 hidden:
diff --git a/core/modules/options/tests/options_config_install_test/config/install/core.entity_view_display.node.options_install_test.teaser.yml b/core/modules/options/tests/options_config_install_test/config/install/core.entity_view_display.node.options_install_test.teaser.yml
index 3b472a7..6e79af9 100644
--- a/core/modules/options/tests/options_config_install_test/config/install/core.entity_view_display.node.options_install_test.teaser.yml
+++ b/core/modules/options/tests/options_config_install_test/config/install/core.entity_view_display.node.options_install_test.teaser.yml
@@ -15,10 +15,12 @@ mode: teaser
 content:
   links:
     weight: 100
+    region: content
   body:
     label: hidden
     type: text_summary_or_trimmed
     weight: 101
+    region: content
     settings:
       trim_length: 600
     third_party_settings: {  }
diff --git a/core/modules/system/system.install b/core/modules/system/system.install
index 154a55b..f732299 100644
--- a/core/modules/system/system.install
+++ b/core/modules/system/system.install
@@ -1680,3 +1680,18 @@ function system_update_8200(&$sandbox) {
   $sandbox['config_names'] = array_diff($sandbox['config_names'], $config_names_to_process);
   $sandbox['#finished'] = empty($sandbox['config_names']) ? 1 : ($sandbox['max'] - count($sandbox['config_names'])) / $sandbox['max'];
 }
+
+/**
+ * Update entity displays to contain the region for each field.
+ */
+function system_update_8201() {
+  $config_factory = \Drupal::configFactory();
+  $config_names = array_merge($config_factory->listAll('core.entity_view_display.'), $config_factory->listAll('core.entity_form_display.'));
+  foreach ($config_names as $name) {
+    $config = $config_factory->getEditable($name);
+    foreach ($config->get('content') as $name => $field) {
+      $config->set('content.' . $name . '.region', 'content');
+    }
+    $config->save(TRUE);
+  }
+}
diff --git a/core/modules/user/src/Entity/User.php b/core/modules/user/src/Entity/User.php
index 030b9a9..d25f7b2 100644
--- a/core/modules/user/src/Entity/User.php
+++ b/core/modules/user/src/Entity/User.php
@@ -435,7 +435,7 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
 
     $fields['langcode']->setLabel(t('Language code'))
       ->setDescription(t('The user language code.'))
-      ->setDisplayOptions('form', ['type' => 'hidden']);
+      ->setDisplayOptions('form', ['region' => 'hidden']);
 
     $fields['preferred_langcode'] = BaseFieldDefinition::create('language')
       ->setLabel(t('Preferred language code'))
diff --git a/core/profiles/standard/config/install/core.entity_form_display.block_content.basic.default.yml b/core/profiles/standard/config/install/core.entity_form_display.block_content.basic.default.yml
index ee0c138..7ccb5b0 100644
--- a/core/profiles/standard/config/install/core.entity_form_display.block_content.basic.default.yml
+++ b/core/profiles/standard/config/install/core.entity_form_display.block_content.basic.default.yml
@@ -14,6 +14,7 @@ content:
   body:
     type: text_textarea_with_summary
     weight: -4
+    region: content
     settings:
       rows: 9
       summary_rows: 3
@@ -22,6 +23,7 @@ content:
   info:
     type: string_textfield
     weight: -5
+    region: content
     settings:
       size: 60
       placeholder: ''
diff --git a/core/profiles/standard/config/install/core.entity_form_display.comment.comment.default.yml b/core/profiles/standard/config/install/core.entity_form_display.comment.comment.default.yml
index fa5d834..1010be2 100644
--- a/core/profiles/standard/config/install/core.entity_form_display.comment.comment.default.yml
+++ b/core/profiles/standard/config/install/core.entity_form_display.comment.comment.default.yml
@@ -13,9 +13,11 @@ mode: default
 content:
   author:
     weight: -2
+    region: content
   comment_body:
     type: text_textarea
     weight: 11
+    region: content
     settings:
       rows: 5
       placeholder: ''
@@ -23,6 +25,7 @@ content:
   subject:
     type: string_textfield
     weight: 10
+    region: content
     settings:
       size: 60
       placeholder: ''
diff --git a/core/profiles/standard/config/install/core.entity_form_display.node.article.default.yml b/core/profiles/standard/config/install/core.entity_form_display.node.article.default.yml
index 79156b2..c94e36e 100644
--- a/core/profiles/standard/config/install/core.entity_form_display.node.article.default.yml
+++ b/core/profiles/standard/config/install/core.entity_form_display.node.article.default.yml
@@ -21,6 +21,7 @@ content:
   body:
     type: text_textarea_with_summary
     weight: 1
+    region: content
     settings:
       rows: 9
       summary_rows: 3
@@ -29,16 +30,19 @@ content:
   comment:
     type: comment_default
     weight: 20
+    region: content
     settings: {  }
     third_party_settings: {  }
   created:
     type: datetime_timestamp
     weight: 10
+    region: content
     settings: {  }
     third_party_settings: {  }
   field_image:
     type: image_image
     weight: 4
+    region: content
     settings:
       progress_indicator: throbber
       preview_image_style: thumbnail
@@ -46,11 +50,13 @@ content:
   field_tags:
     type: entity_reference_autocomplete_tags
     weight: 3
+    region: content
     settings: {  }
     third_party_settings: {  }
   path:
     type: path
     weight: 30
+    region: content
     settings: {  }
     third_party_settings: {  }
   promote:
@@ -58,16 +64,19 @@ content:
     settings:
       display_label: true
     weight: 15
+    region: content
     third_party_settings: {  }
   sticky:
     type: boolean_checkbox
     settings:
       display_label: true
     weight: 16
+    region: content
     third_party_settings: {  }
   title:
     type: string_textfield
     weight: 0
+    region: content
     settings:
       size: 60
       placeholder: ''
@@ -75,6 +84,7 @@ content:
   uid:
     type: entity_reference_autocomplete
     weight: 5
+    region: content
     settings:
       match_operator: CONTAINS
       size: 60
diff --git a/core/profiles/standard/config/install/core.entity_form_display.node.page.default.yml b/core/profiles/standard/config/install/core.entity_form_display.node.page.default.yml
index 1fef06d..0b7ffd1 100644
--- a/core/profiles/standard/config/install/core.entity_form_display.node.page.default.yml
+++ b/core/profiles/standard/config/install/core.entity_form_display.node.page.default.yml
@@ -15,6 +15,7 @@ content:
   body:
     type: text_textarea_with_summary
     weight: 31
+    region: content
     settings:
       rows: 9
       summary_rows: 3
@@ -23,11 +24,13 @@ content:
   created:
     type: datetime_timestamp
     weight: 10
+    region: content
     settings: {  }
     third_party_settings: {  }
   path:
     type: path
     weight: 30
+    region: content
     settings: {  }
     third_party_settings: {  }
   promote:
@@ -35,16 +38,19 @@ content:
     settings:
       display_label: true
     weight: 15
+    region: content
     third_party_settings: {  }
   sticky:
     type: boolean_checkbox
     settings:
       display_label: true
     weight: 16
+    region: content
     third_party_settings: {  }
   title:
     type: string_textfield
     weight: -5
+    region: content
     settings:
       size: 60
       placeholder: ''
@@ -52,6 +58,7 @@ content:
   uid:
     type: entity_reference_autocomplete
     weight: 5
+    region: content
     settings:
       match_operator: CONTAINS
       size: 60
diff --git a/core/profiles/standard/config/install/core.entity_form_display.user.user.default.yml b/core/profiles/standard/config/install/core.entity_form_display.user.user.default.yml
index 466b6e0..6832229 100644
--- a/core/profiles/standard/config/install/core.entity_form_display.user.user.default.yml
+++ b/core/profiles/standard/config/install/core.entity_form_display.user.user.default.yml
@@ -14,12 +14,16 @@ mode: default
 content:
   account:
     weight: -10
+    region: content
   contact:
     weight: 5
+    region: content
   language:
     weight: 0
+    region: content
   timezone:
     weight: 6
+    region: content
   user_picture:
     type: image_image
     settings:
@@ -27,4 +31,5 @@ content:
       preview_image_style: thumbnail
     third_party_settings: {  }
     weight: -1
+    region: content
 hidden: {  }
diff --git a/core/profiles/standard/config/install/core.entity_view_display.block_content.basic.default.yml b/core/profiles/standard/config/install/core.entity_view_display.block_content.basic.default.yml
index bd52f77..e494882 100644
--- a/core/profiles/standard/config/install/core.entity_view_display.block_content.basic.default.yml
+++ b/core/profiles/standard/config/install/core.entity_view_display.block_content.basic.default.yml
@@ -15,6 +15,7 @@ content:
     label: hidden
     type: text_default
     weight: 0
+    region: content
     settings: {  }
     third_party_settings: {  }
 hidden: {  }
diff --git a/core/profiles/standard/config/install/core.entity_view_display.comment.comment.default.yml b/core/profiles/standard/config/install/core.entity_view_display.comment.comment.default.yml
index 1ed49ce..6ae213d 100644
--- a/core/profiles/standard/config/install/core.entity_view_display.comment.comment.default.yml
+++ b/core/profiles/standard/config/install/core.entity_view_display.comment.comment.default.yml
@@ -15,8 +15,10 @@ content:
     label: hidden
     type: text_default
     weight: 0
+    region: content
     settings: {  }
     third_party_settings: {  }
   links:
     weight: 100
+    region: content
 hidden: {  }
diff --git a/core/profiles/standard/config/install/core.entity_view_display.node.article.default.yml b/core/profiles/standard/config/install/core.entity_view_display.node.article.default.yml
index 98a2de8..5c43252 100644
--- a/core/profiles/standard/config/install/core.entity_view_display.node.article.default.yml
+++ b/core/profiles/standard/config/install/core.entity_view_display.node.article.default.yml
@@ -22,12 +22,14 @@ content:
   body:
     type: text_default
     weight: 0
+    region: content
     settings: {  }
     third_party_settings: {  }
     label: hidden
   comment:
     type: comment_default
     weight: 110
+    region: content
     label: above
     settings:
       view_mode: default
@@ -36,6 +38,7 @@ content:
   field_image:
     type: image
     weight: -1
+    region: content
     settings:
       image_style: large
       image_link: ''
@@ -44,12 +47,14 @@ content:
   field_tags:
     type: entity_reference_label
     weight: 10
+    region: content
     label: above
     settings:
       link: true
     third_party_settings: {  }
   links:
     weight: 100
+    region: content
 hidden:
   field_image: true
   field_tags: true
diff --git a/core/profiles/standard/config/install/core.entity_view_display.node.article.rss.yml b/core/profiles/standard/config/install/core.entity_view_display.node.article.rss.yml
index 75a14a3..84660b6 100644
--- a/core/profiles/standard/config/install/core.entity_view_display.node.article.rss.yml
+++ b/core/profiles/standard/config/install/core.entity_view_display.node.article.rss.yml
@@ -17,6 +17,7 @@ mode: rss
 content:
   links:
     weight: 100
+    region: content
 hidden:
   body: true
   comment: true
diff --git a/core/profiles/standard/config/install/core.entity_view_display.node.article.teaser.yml b/core/profiles/standard/config/install/core.entity_view_display.node.article.teaser.yml
index 43ee079..7b96908 100644
--- a/core/profiles/standard/config/install/core.entity_view_display.node.article.teaser.yml
+++ b/core/profiles/standard/config/install/core.entity_view_display.node.article.teaser.yml
@@ -21,6 +21,7 @@ content:
   body:
     type: text_summary_or_trimmed
     weight: 0
+    region: content
     settings:
       trim_length: 600
     third_party_settings: {  }
@@ -28,6 +29,7 @@ content:
   field_image:
     type: image
     weight: -1
+    region: content
     settings:
       image_style: medium
       image_link: content
@@ -36,12 +38,14 @@ content:
   field_tags:
     type: entity_reference_label
     weight: 10
+    region: content
     settings:
       link: true
     third_party_settings: {  }
     label: above
   links:
     weight: 100
+    region: content
 hidden:
   comment: true
   field_image: true
diff --git a/core/profiles/standard/config/install/core.entity_view_display.node.page.default.yml b/core/profiles/standard/config/install/core.entity_view_display.node.page.default.yml
index dcb2d3e..8afd942 100644
--- a/core/profiles/standard/config/install/core.entity_view_display.node.page.default.yml
+++ b/core/profiles/standard/config/install/core.entity_view_display.node.page.default.yml
@@ -16,8 +16,10 @@ content:
     label: hidden
     type: text_default
     weight: 100
+    region: content
     settings: {  }
     third_party_settings: {  }
   links:
     weight: 101
+    region: content
 hidden: {  }
diff --git a/core/profiles/standard/config/install/core.entity_view_display.node.page.teaser.yml b/core/profiles/standard/config/install/core.entity_view_display.node.page.teaser.yml
index f235a10..bc7a68c 100644
--- a/core/profiles/standard/config/install/core.entity_view_display.node.page.teaser.yml
+++ b/core/profiles/standard/config/install/core.entity_view_display.node.page.teaser.yml
@@ -17,9 +17,11 @@ content:
     label: hidden
     type: text_summary_or_trimmed
     weight: 100
+    region: content
     settings:
       trim_length: 600
     third_party_settings: {  }
   links:
     weight: 101
+    region: content
 hidden: {  }
diff --git a/core/profiles/standard/config/install/core.entity_view_display.user.user.compact.yml b/core/profiles/standard/config/install/core.entity_view_display.user.user.compact.yml
index 4c13792..2ff13ad 100644
--- a/core/profiles/standard/config/install/core.entity_view_display.user.user.compact.yml
+++ b/core/profiles/standard/config/install/core.entity_view_display.user.user.compact.yml
@@ -16,6 +16,7 @@ content:
   user_picture:
     type: image
     weight: 0
+    region: content
     settings:
       image_style: thumbnail
       image_link: content
diff --git a/core/profiles/standard/config/install/core.entity_view_display.user.user.default.yml b/core/profiles/standard/config/install/core.entity_view_display.user.user.default.yml
index 9e4621d..ef1fdd7 100644
--- a/core/profiles/standard/config/install/core.entity_view_display.user.user.default.yml
+++ b/core/profiles/standard/config/install/core.entity_view_display.user.user.default.yml
@@ -14,9 +14,11 @@ mode: default
 content:
   member_for:
     weight: 5
+    region: content
   user_picture:
     type: image
     weight: 0
+    region: content
     settings:
       image_style: thumbnail
       image_link: content
diff --git a/core/tests/Drupal/KernelTests/Core/Entity/EntityDisplayFormBaseTest.php b/core/tests/Drupal/KernelTests/Core/Entity/EntityDisplayFormBaseTest.php
new file mode 100644
index 0000000..c128365
--- /dev/null
+++ b/core/tests/Drupal/KernelTests/Core/Entity/EntityDisplayFormBaseTest.php
@@ -0,0 +1,143 @@
+<?php
+
+namespace Drupal\KernelTests\Core\Entity;
+
+use Drupal\Core\Entity\Display\EntityDisplayInterface;
+use Drupal\Core\Form\FormState;
+use Drupal\field_ui\Form\EntityViewDisplayEditForm;
+use Drupal\KernelTests\KernelTestBase;
+
+/**
+ * @coversDefaultClass \Drupal\field_ui\Form\EntityDisplayFormBase
+ *
+ * @group Entity
+ */
+class EntityDisplayFormBaseTest extends KernelTestBase {
+
+  /**
+   * @covers ::determineComponentAction
+   */
+  public function testDetermineComponentAction() {
+    $entity = $this->prophesize(EntityDisplayInterface::class);
+    $entity->getPluginCollections()->willReturn([]);
+    $entity->getComponent('field_hidden_no_changes')->willReturn([
+      'weight' => 0,
+      'type' => 'hidden',
+      'region' => 'hidden',
+    ]);
+    $entity->removeComponent('field_hidden_no_changes')->shouldBeCalled();
+    $entity->getComponent('field_visible_no_changes')->willReturn([
+      'weight' => 0,
+      'type' => 'textfield',
+      'region' => 'content',
+    ]);
+    $entity->setComponent('field_visible_no_changes', [
+      'weight' => 0,
+      'type' => 'textfield',
+      'region' => 'content',
+    ])->shouldBeCalled();
+    $entity->getComponent('field_start_hidden_change_type')->willReturn([
+      'weight' => 0,
+      'type' => 'hidden',
+      'region' => 'hidden',
+    ]);
+    $entity->setComponent('field_start_hidden_change_type', [
+      'weight' => 0,
+      'type' => 'textfield',
+    ])->shouldBeCalled();
+    $entity->getComponent('field_start_hidden_change_region')->willReturn([
+      'weight' => 0,
+      'type' => 'hidden',
+      'region' => 'hidden',
+    ]);
+    $entity->setComponent('field_start_hidden_change_region', [
+      'weight' => 0,
+      'region' => 'content',
+    ])->shouldBeCalled();
+    $entity->getComponent('field_start_hidden_change_both')->willReturn([
+      'weight' => 0,
+      'type' => 'hidden',
+      'region' => 'hidden',
+    ]);
+    $entity->setComponent('field_start_hidden_change_both', [
+      'weight' => 0,
+      'type' => 'textfield',
+      'region' => 'content',
+    ])->shouldBeCalled();
+    $entity->getComponent('field_start_visible_change_type')->willReturn([
+      'weight' => 0,
+      'type' => 'textfield',
+      'region' => 'content',
+    ]);
+    $entity->removeComponent('field_start_visible_change_type')->shouldBeCalled();
+    $entity->getComponent('field_start_visible_change_region')->willReturn([
+      'weight' => 0,
+      'type' => 'textfield',
+      'region' => 'content',
+    ]);
+    $entity->removeComponent('field_start_visible_change_region')->shouldBeCalled();
+    $entity->getComponent('field_start_visible_change_both')->willReturn([
+      'weight' => 0,
+      'type' => 'textfield',
+      'region' => 'content',
+    ]);
+    $entity->removeComponent('field_start_visible_change_both')->shouldBeCalled();
+    $entity->save()->shouldBeCalled();
+
+    $fields = [
+      'field_hidden_no_changes' => [
+        'weight' => 0,
+        'type' => 'hidden',
+        'region' => 'hidden',
+      ],
+      'field_visible_no_changes' => [
+        'weight' => 0,
+        'type' => 'textfield',
+        'region' => 'content',
+      ],
+      'field_start_hidden_change_type' => [
+        'weight' => 0,
+        'type' => 'textfield',
+        'region' => 'hidden',
+      ],
+      'field_start_hidden_change_region' => [
+        'weight' => 0,
+        'type' => 'hidden',
+        'region' => 'content',
+      ],
+      'field_start_hidden_change_both' => [
+        'weight' => 0,
+        'type' => 'textfield',
+        'region' => 'content',
+      ],
+      'field_start_visible_change_type' => [
+        'weight' => 0,
+        'type' => 'hidden',
+        'region' => 'content',
+      ],
+      'field_start_visible_change_region' => [
+        'weight' => 0,
+        'type' => 'textfield',
+        'region' => 'hidden',
+      ],
+      'field_start_visible_change_both' => [
+        'weight' => 0,
+        'type' => 'hidden',
+        'region' => 'hidden',
+      ],
+    ];
+
+    $form = [
+      '#fields' => array_keys($fields),
+      '#extra' => [],
+    ];
+    $form_state = (new FormState())->setFormState(['values' => ['fields' => $fields]]);
+
+    $form_object = new EntityViewDisplayEditForm($this->container->get('plugin.manager.field.field_type'), $this->container->get('plugin.manager.field.formatter'));
+    $form_object->setEntity($entity->reveal());
+    $form_object->buildEntity($form, $form_state);
+    $form_object->save($form, $form_state);
+    $form_object->buildEntity($form, $form_state);
+  }
+
+}
