diff --git a/config/schema/panelizer.schema.yml b/config/schema/panelizer.schema.yml
index 9b91bd3..c7d7b1e 100644
--- a/config/schema/panelizer.schema.yml
+++ b/config/schema/panelizer.schema.yml
@@ -1,4 +1,12 @@
 # Config schema for Panelizer.
+panelizer.default.display:
+  type: display_variant.plugin.panels_variant
+  label: 'Panelizer display default'
+  mapping:
+    static_context:
+      type: ctools.context
+      label: 'Contexts'
+
 
 core.entity_view_display.*.*.*.third_party.panelizer:
   type: mapping
@@ -13,6 +21,6 @@ core.entity_view_display.*.*.*.third_party.panelizer:
       type: sequence
       description: 'Default displays'
       sequence:
-        type: display_variant.plugin.panels_variant
-        description: 'Default display'
+        type: panelizer.default.display
+        description: 'Default displays'
 
diff --git a/css/panelizer.admin.css b/css/panelizer.admin.css
new file mode 100644
index 0000000..1aa3794
--- /dev/null
+++ b/css/panelizer.admin.css
@@ -0,0 +1,113 @@
+/**
+ * @file
+ * Styles for Panelizer wizard admin.
+ */
+
+/* Narrow screens */
+
+.panelizer-wizard-tree,
+.panelizer-wizard-form {
+  box-sizing: border-box;
+}
+
+/**
+ * Wizard actions across the top.
+ */
+.panelizer-wizard-actions {
+  text-align: right; /* LTR */
+}
+.panelizer-wizard-actions ul.inline,
+.panelizer-wizard-actions ul.inline li {
+  display: inline-block;
+  margin: 0;
+}
+.panelizer-wizard-actions ul.inline {
+  border-top: 1px solid black;
+  border-left: 1px solid black;
+}
+.panelizer-wizard-actions ul.inline li {
+  border-right: 1px solid black;
+  padding: .5em;
+}
+
+/**
+ * The tree of wizard steps.
+ */
+.panelizer-wizard-tree ul {
+  margin: 0;
+  padding: 0;
+  list-style: none;
+}
+.panelizer-wizard-tree ul > li > ul {
+  margin-left: 1em;
+}
+.panelizer-wizard-tree > ul {
+  border: 1px solid black;
+  padding-bottom: .5em;
+  margin-bottom: 20px;
+}
+.panelizer-wizard-tree li {
+  border-bottom: 1px solid black;
+  padding: .5em;
+  padding-right: 0;
+}
+.panelizer-wizard-tree li:last-child {
+  border-bottom: 0;
+  padding-bottom: 0;
+}
+
+/**
+ * The wizard form.
+ */
+.panelizer-wizard-form {
+  border: 1px solid black;
+  padding: 1em;
+  margin-bottom: 20px;
+}
+
+/* Wide screens */
+@media
+  screen and (min-width: 780px),
+  (orientation: landscape) and (min-device-height: 780px) {
+
+  /**
+   * Overall layout.
+   */
+  .panelizer-wizard-tree {
+    float: left; /* LTR */
+    width: 20%;
+  }
+  .panelizer-wizard-form {
+    float: left; /* LTR */
+    width: 80%;
+  }
+  .panelizer-wizard-form-actions {
+    margin-left: 20%; /* LTR */
+  }
+
+  /**
+   * Make the borders look nice.
+   */
+  .panelizer-wizard-tree > ul {
+    border-right: 0; /* LTR */
+  }
+  .panelizer-wizard-form {
+    min-height: 400px;
+  }
+
+  /**
+   * Right-to-left support.
+   */
+  [dir="rtl"] .panelizer-wizard-tree,
+  [dir="rtl"] .panelizer-wizard-form {
+    float: right;
+  }
+  [dir="rtl"] .panelizer-wizard-form-actions {
+    margin-left: 0;
+    margin-right: 20%;
+  }
+  [dir="rtl"] .panelizer-wizard-tree > ul {
+    border-right: 1px solid black;
+    border-left: 0;
+  }
+}
diff --git a/panelizer.libraries.yml b/panelizer.libraries.yml
index 6e9a5b2..50b9c76 100644
--- a/panelizer.libraries.yml
+++ b/panelizer.libraries.yml
@@ -17,4 +17,8 @@ panels_ipe:
       css/panels_ipe.css: {}
   dependencies:
     - panels_ipe/panels_ipe
-
+wizard_admin:
+  version: VERSION
+  css:
+    layout:
+      css/panelizer.admin.css: {}
diff --git a/panelizer.links.action.yml b/panelizer.links.action.yml
new file mode 100644
index 0000000..14e71da
--- /dev/null
+++ b/panelizer.links.action.yml
@@ -0,0 +1,5 @@
+panelizer.default.add:
+  route_name: panelizer.wizard.add
+  title: 'Add panelizer default'
+  class: \Drupal\panelizer\Menu\AddDefaultLocalAction
+  deriver: \Drupal\panelizer\Plugin\AddDefaultLinkDeriver
diff --git a/panelizer.module b/panelizer.module
index 2672b1b..77d6f5b 100644
--- a/panelizer.module
+++ b/panelizer.module
@@ -4,10 +4,11 @@
  * Hook implementations for the Panelizer module.
  */
 
-use \Drupal\Core\Entity\FieldableEntityInterface;
-use \Drupal\Core\Entity\RevisionableInterface;
-use \Drupal\Core\Form\FormStateInterface;
-use \Drupal\panels\Plugin\DisplayVariant\PanelsDisplayVariant;
+use Drupal\Core\Entity\FieldableEntityInterface;
+use Drupal\Core\Entity\RevisionableInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Url;
+use Drupal\panels\Plugin\DisplayVariant\PanelsDisplayVariant;
 
 /**
  * Implements hook_theme().
@@ -17,10 +18,62 @@ function panelizer_theme() {
     'panelizer_view_mode' => [
       'render element' => 'element',
     ],
+    'panelizer_wizard_form' => [
+      'render element' => 'form',
+    ],
+    'panelizer_wizard_tree' => [
+      'variables' => [
+        'wizard' => NULL,
+        'cached_values' => [],
+        'tree' => [],
+        'divider' => ' » ',
+        'step' => NULL,
+      ],
+    ],
   ];
 }
 
 /**
+ * Preprocess function for panelizer-wizard-tree.html.twig.
+ */
+function template_preprocess_panelizer_wizard_tree(&$variables) {
+  /** @var $wizard \Drupal\ctools\Wizard\FormWizardInterface|\Drupal\ctools\Wizard\EntityFormWizardInterface */
+  $wizard = $variables['wizard'];
+  $cached_values = $variables['cached_values'];
+  $tree = $variables['tree'];
+  $variables['step'] = $wizard->getStep($cached_values);
+
+  foreach ($wizard->getOperations($cached_values) as $step => $operation) {
+    $parameters = $wizard->getNextParameters($cached_values);
+    // Override step to be the step we want.
+    $parameters['step'] = $step;
+
+    // Fill in parents if there are breadcrumbs.
+    $parent =& $tree;
+    if (isset($operation['breadcrumbs'])) {
+      foreach ($operation['breadcrumbs'] as $breadcrumb) {
+        $breadcrumb_string = (string) $breadcrumb;
+        if (!isset($parent[$breadcrumb_string])) {
+          $parent[$breadcrumb_string] = [
+            'title' => $breadcrumb,
+            'children' => [],
+          ];
+        }
+        $parent =& $parent[$breadcrumb_string]['children'];
+      }
+    }
+
+    $parent[$step] = [
+      'title' => !empty($operation['title']) ? $operation['title'] : '',
+      'url' => new \Drupal\Core\Url($wizard->getRouteName(), $parameters),
+      'step' => $step,
+    ];
+  }
+
+  $variables['tree'] = $tree;
+}
+
+/**
  * Implements hook_entity_type_alter().
  */
 function panelizer_entity_type_alter(array &$entity_types) {
@@ -161,45 +214,90 @@ function panelizer_form_entity_view_display_edit_form_alter(&$form, FormStateInt
     $form['panelizer'] = [
       '#tree' => TRUE,
     ];
+    // If this display mode is panelized, then show a link to its settings.
+    if (!empty($settings['enable'])) {
+      $form['panelizer']['displays'] = [
+        '#type' => 'table',
+        '#caption' => t('Panelized Displays'),
+        '#header' => [t('Label'), t('Default'), t('Operations')],
+      ];
+      $operations = [];
+      foreach ($display->getThirdPartySetting('panelizer', 'displays', []) as $machine_name => $panels_display) {
+        $display_name = $machine_name;
+        $machine_name ="{$display->getTargetEntityTypeId()}__{$display->getTargetBundle()}__{$display->getMode()}__$machine_name";
+        $operations['edit'] = [
+          'title' => t('Edit'),
+          'url' => Url::fromRoute('panelizer.wizard.edit', ['machine_name' => $machine_name]),
+        ];
+        if ($settings['default'] != $display_name) {
+          $operations['set_default'] = [
+            'title' => t('Set default'),
+            'url' => Url::fromRoute('panelizer.default.select', ['machine_name' => $machine_name]),
+          ];
+        }
+        $operations['delete'] = [
+          'title' => t('Delete'),
+          'url' => Url::fromRoute('panelizer.default.delete', ['machine_name' => $machine_name]),
+        ];
+        $form['panelizer']['displays'][$machine_name] = [
+          'label' => ['#markup' => $panels_display['label']],
+          'default' => ['#markup' => $settings['default'] == $display_name ? 'TRUE' : 'FALSE'],
+          'operations' => [
+            'data' => [
+              '#type' => 'operations',
+              '#links' => $operations,
+            ]
+          ]
+        ];
+      }
+      $form['fields']['#access'] = FALSE;
+    }
     $form['panelizer']['enable'] = [
       '#type' => 'checkbox',
       '#title' => t('Panelize this view mode'),
-      '#default_value' => !empty($settings['enable']),
+      '#default_value' => isset($settings['enable']) ? $settings['enable'] : FALSE,
     ];
+
     $form['panelizer']['custom'] = [
       '#type' => 'checkbox',
       '#title' => t('Allow custom overrides of each entity'),
-      '#default_value' => !empty($settings['custom']),
-      '#states' => [
-        'visible' => [
-          ':input[name="panelizer[enable]"]' => ['checked' => TRUE],
-        ]
-      ]
+      '#default_value' => isset($settings['custom']) ? $settings['custom'] : FALSE,
     ];
 
     $form['#attached']['library'][] = 'panelizer/panelizer_default_form';
-
-    $form['actions']['submit']['#submit'][] = 'panelizer_form_entity_view_display_edit_form_submit';
+    $form['actions']['submit']['#submit'][] = 'panelizer_form_entity_view_display_edit_submit';
   }
 }
 
-/**
- * Form submission callback for entity_view_display_edit_form.
- */
-function panelizer_form_entity_view_display_edit_form_submit($form, FormStateInterface $form_state) {
+function panelizer_form_entity_view_display_edit_submit(&$form, FormStateInterface $form_state) {
+  $rebuild = FALSE;
   /** @var \Drupal\Core\Entity\EntityFormInterface $form_object */
   $form_object = $form_state->getFormObject();
   /** @var \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display */
   $display = $form_object->getEntity();
-
-  $settings = [
-    'enable' => $form_state->getValue(['panelizer', 'enable']),
-    'custom' => $form_state->getValue(['panelizer', 'custom']),
-  ];
-
-  /** @var \Drupal\panelizer\PanelizerInterface $panelizer */
+  /** @var \Drupal\panelizer\Plugin\PanelizerEntityManager $panelizer_manager */
+  $panelizer_manager = \Drupal::service('plugin.manager.panelizer_entity');
+  /** @var \Drupal\panelizer\Panelizer $panelizer */
   $panelizer = \Drupal::service('panelizer');
-  $panelizer->setPanelizerSettings($display->getTargetEntityTypeId(), $display->getTargetBundle(), $display->getMode(), $settings, $display);
+
+  if ($panelizer_manager->hasDefinition($display->getTargetEntityTypeId())) {
+    $settings = $panelizer->getPanelizerSettings($display->getTargetEntityTypeId(), $display->getTargetBundle(), $display->getMode(), $display);
+    if ($settings['enable'] != $form_state->getValue(['panelizer', 'enable'])) {
+      $rebuild = TRUE;
+    }
+    $settings['enable'] = $form_state->getValue(['panelizer', 'enable']);
+    $settings['custom'] = $form_state->getValue(['panelizer', 'custom']);
+    $panelizer->setPanelizerSettings($display->getTargetEntityTypeId(), $display->getTargetBundle(), $display->getMode(), $settings, $display);
+    if ($rebuild) {
+      \Drupal::service('router.builder')->rebuild();
+      /** @var \Drupal\Core\Menu\LocalActionManager $local_action_manager */
+      $local_action_manager = \Drupal::service('plugin.manager.menu.local_action');
+      $local_action_manager->clearCachedDefinitions();
+      // Manually reinitialize these.
+      $local_action_manager->getDefinitions();
+      \Drupal::service('cache.render')->invalidateAll();
+    }
+  }
 }
 
 /**
diff --git a/panelizer.routing.yml b/panelizer.routing.yml
index feac328..9443766 100644
--- a/panelizer.routing.yml
+++ b/panelizer.routing.yml
@@ -12,3 +12,114 @@ panelizer.panels_ipe.revert_to_default:
     _method: 'POST'
     _permission: 'access panels in-place editing'
     _custom_access: '\Drupal\panelizer\Controller\PanelizerPanelsIPEController::accessRevertToDefault'
+
+# Wizard
+panelizer.wizard.add:
+  path: '/admin/structure/panelizer/add/{entity_type_id}/{bundle}/{view_mode_name}'
+  defaults:
+    _wizard: '\Drupal\panelizer\Wizard\PanelizerAddWizard'
+    _title: 'Panelizer Wizard'
+    tempstore_id: 'panelizer.wizard'
+  requirements:
+    _panelizer_default_access: 'TRUE'
+    _permission: 'administer panelizer'
+
+panelizer.wizard.add.step:
+  path: '/admin/structure/panelizer/add/{machine_name}/{step}'
+  defaults:
+    _wizard: '\Drupal\panelizer\Wizard\PanelizerAddWizard'
+    _title: 'Panelizer Wizard'
+    tempstore_id: 'panelizer.wizard'
+  requirements:
+    _permission: 'administer panelizer'
+
+panelizer.wizard.edit:
+  path: '/admin/structure/panelizer/edit/{machine_name}/{step}'
+  defaults:
+    _wizard: '\Drupal\panelizer\Wizard\PanelizerEditWizard'
+    _title: 'Panelizer Wizard'
+    tempstore_id: 'panelizer.wizard'
+    step: 'general'
+  requirements:
+    _permission: 'administer panelizer'
+
+panelizer.default.delete:
+  path: '/admin/structure/panelizer/delete/{machine_name}'
+  defaults:
+    _form: '\Drupal\panelizer\Form\PanelizerDefaultDelete'
+    _title: 'Delete panelizer default'
+  requirements:
+    _permission: 'administer panelizer'
+
+panelizer.default.select:
+  path: '/admin/structure/panelizer/set_default/{machine_name}'
+  defaults:
+    _form: '\Drupal\panelizer\Form\PanelizerDefaultSelect'
+    _title: 'Set as default'
+  requirements:
+    _permission: 'administer panelizer'
+
+# Contexts
+panelizer.wizard.step.context.add:
+  path: '/admin/panelizer/wizard/{machine_name}/contexts/add/{context_id}'
+  defaults:
+    _form: '\Drupal\panelizer\Form\PanelizerWizardContextConfigure'
+    _title: 'Add custom context'
+    tempstore_id: 'panelizer.wizard'
+  requirements:
+    _permission: 'administer pages'
+
+panelizer.wizard.step.context.edit:
+  path: '/admin/panelizer/wizard/{machine_name}/contexts/edit/{context_id}'
+  defaults:
+    _form: '\Drupal\panelizer\Form\PanelizerWizardContextConfigure'
+    _title: 'Edit context'
+    tempstore_id: 'panelizer.wizard'
+  requirements:
+    _permission: 'administer pages'
+
+panelizer.wizard.step.context.delete:
+  path: '/admin/panelizer/wizard/{machine_name}/context/delete/{context_id}'
+  defaults:
+    _form: '\Drupal\panelizer\Form\PanelizerWizardContextDeleteForm'
+    _title: 'Delete static context'
+    tempstore_id: 'panelizer.wizard'
+  requirements:
+    _permission: 'administer pages'
+
+# Content blocks
+panelizer.wizard.step.content.select_block:
+  path: '/admin/panelizer/wizard/block_display/{machine_name}/select'
+  defaults:
+    _controller: '\Drupal\panelizer\Controller\PanelizerWizardController::selectBlock'
+    _title: 'Select block'
+    tempstore_id: 'panelizer.wizard'
+  requirements:
+    _permission: 'administer pages'
+
+panelizer.wizard.step.content.add_block:
+  path: '/admin/panelizer/wizard/block_display/{machine_name}/add/{block_id}'
+  defaults:
+    _form: '\Drupal\panelizer\Form\PanelizerWizardAddBlockForm'
+    _title: 'Add block'
+    tempstore_id: 'panelizer.wizard'
+  requirements:
+    _permission: 'administer pages'
+
+panelizer.wizard.step.content.edit_block:
+  path: '/admin/panelizer/wizard/block_display/{machine_name}/edit/{block_id}'
+  defaults:
+    _form: '\Drupal\panelizer\Form\PanelizerWizardEditBlockForm'
+    _title: 'Edit block'
+    tempstore_id: 'panelizer.block_display'
+  requirements:
+    _permission: 'administer pages'
+
+panelizer.wizard.step.content.delete_block:
+  path: '/admin/panelizer/wizard/block_display/{machine_name}/delete/{block_id}'
+  defaults:
+    _form: '\Drupal\panelizer\Form\PanelizerWizardDeleteBlockForm'
+    _title: 'Delete block'
+    tempstore_id: 'panelizer.block_display'
+  requirements:
+    _permission: 'administer pages'
diff --git a/panelizer.services.yml b/panelizer.services.yml
index 20c1b5d..138869a 100644
--- a/panelizer.services.yml
+++ b/panelizer.services.yml
@@ -4,4 +4,9 @@ services:
     parent: default_plugin_manager
   panelizer:
     class: Drupal\panelizer\Panelizer
-    arguments: ['@entity_type.manager', '@entity_type.bundle.info', '@entity_field.manager', '@plugin.manager.field.field_type', '@module_handler', '@current_user', '@plugin.manager.panelizer_entity', '@panels.display_manager', '@string_translation']
+    arguments: ['@entity_type.manager', '@entity_type.bundle.info', '@entity_field.manager', '@plugin.manager.field.field_type', '@module_handler', '@current_user', '@plugin.manager.panelizer_entity', '@panels.display_manager', '@string_translation', '@ctools.context_mapper']
+  panerlizer.default.access:
+    class: Drupal\panelizer\Access\DefaultAccess
+    arguments: ['@entity_type.manager', '@plugin.manager.panelizer_entity', '@request_stack']
+    tags:
+      - { name: access_check, applies_to: _panelizer_default_access }
diff --git a/src/Access/DefaultAccess.php b/src/Access/DefaultAccess.php
new file mode 100644
index 0000000..f71d323
--- /dev/null
+++ b/src/Access/DefaultAccess.php
@@ -0,0 +1,41 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\panelizer\Access\DefaultAccess.
+ */
+
+namespace Drupal\panelizer\Access;
+
+use Drupal\Core\Access\AccessResult;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Routing\Access\AccessInterface;
+use Drupal\panelizer\Plugin\PanelizerEntityManagerInterface;
+
+class DefaultAccess implements AccessInterface {
+
+  /**
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * @var \Drupal\panelizer\Plugin\PanelizerEntityManagerInterface
+   */
+  protected $panelizerEntityManager;
+
+  public function __construct(EntityTypeManagerInterface $entity_type_manager, PanelizerEntityManagerInterface $panelizer_entity_manager) {
+    $this->entityTypeManager = $entity_type_manager;
+    $this->panelizerEntityManager = $panelizer_entity_manager;
+  }
+
+  public function access($entity_type_id, $bundle, $view_mode_name) {
+    /** @var \Drupal\panelizer\PanelizerInterface $panelizer */
+    $panelizer = \Drupal::service('panelizer');
+    $settings = $panelizer->getPanelizerSettings($entity_type_id, $bundle, $view_mode_name);
+
+    //$view_mode = $route->getDefault('view_mode');
+    return $settings['enable'] ? AccessResult::allowed() : AccessResult::forbidden();
+  }
+
+}
diff --git a/src/Controller/PanelizerWizardController.php b/src/Controller/PanelizerWizardController.php
new file mode 100644
index 0000000..a0d5b29
--- /dev/null
+++ b/src/Controller/PanelizerWizardController.php
@@ -0,0 +1,165 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\panelizer\Controller\PanelizerWizardController.
+ */
+
+namespace Drupal\panelizer\Controller;
+
+use Drupal\Component\Plugin\PluginManagerInterface;
+use Drupal\Core\Block\BlockManagerInterface;
+use Drupal\Core\Controller\ControllerBase;
+use Drupal\Core\Plugin\Context\Context;
+use Drupal\Core\Plugin\Context\ContextDefinition;
+use Drupal\Core\Plugin\Context\ContextHandlerInterface;
+use Drupal\Core\Url;
+use Drupal\ctools\Form\AjaxFormTrait;
+use Drupal\user\SharedTempStoreFactory;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Provides route controllers for the Panelizer wizard.
+ */
+class PanelizerWizardController extends ControllerBase {
+
+  use AjaxFormTrait;
+
+  /**
+   * The block manager.
+   *
+   * @var \Drupal\Core\Block\BlockManagerInterface
+   */
+  protected $blockManager;
+
+  /**
+   * The condition manager.
+   *
+   * @var \Drupal\Core\Plugin\Context\ContextAwarePluginManagerInterface
+   */
+  protected $conditionManager;
+
+  /**
+   * The variant manager.
+   *
+   * @var \Drupal\Component\Plugin\PluginManagerInterface
+   */
+  protected $variantManager;
+
+  /**
+   * The context handler.
+   *
+   * @var \Drupal\Core\Plugin\Context\ContextHandlerInterface
+   */
+  protected $contextHandler;
+
+  /**
+   * Tempstore factory.
+   *
+   * @var \Drupal\user\SharedTempStoreFactory
+   */
+  protected $tempstore;
+
+  /**
+   * Constructs a new VariantPluginEditForm.
+   *
+   * @param \Drupal\Core\Block\BlockManagerInterface $block_manager
+   *   The block manager.
+   * @param \Drupal\Component\Plugin\PluginManagerInterface $condition_manager
+   *   The condition manager.
+   * @param \Drupal\Component\Plugin\PluginManagerInterface $variant_manager
+   *   The variant manager.
+   * @param \Drupal\Core\Plugin\Context\ContextHandlerInterface $context_handler
+   *   The context handler.
++   * @param \Drupal\user\SharedTempStoreFactory $tempstore
++   *   The tempstore factory.
+   */
+  public function __construct(BlockManagerInterface $block_manager, PluginManagerInterface $condition_manager, PluginManagerInterface $variant_manager, ContextHandlerInterface $context_handler, SharedTempStoreFactory $tempstore) {
+    $this->blockManager = $block_manager;
+    $this->conditionManager = $condition_manager;
+    $this->variantManager = $variant_manager;
+    $this->contextHandler = $context_handler;
+    $this->tempstore = $tempstore;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('plugin.manager.block'),
+      $container->get('plugin.manager.condition'),
+      $container->get('plugin.manager.display_variant'),
+      $container->get('context.handler'),
+      $container->get('user.shared_tempstore')
+    );
+  }
+
+  /**
+   * Presents a list of blocks to add to the variant.
+   *
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The current request.
+   * @param string $machine_name
+   *   The identifier of the block display variant.
+   * @param string $tempstore_id
+   *   The identifier of the temporary store.
+   *
+   * @return array
+   *   The block selection page.
+   */
+  public function selectBlock(Request $request, $machine_name, $tempstore_id) {
+    $cached_values = $this->tempstore->get($tempstore_id)->get($machine_name);
+    /** @var \Drupal\page_manager\Plugin\DisplayVariant\PageBlockDisplayVariant $variant_plugin */
+    $variant_plugin = $cached_values['plugin'];
+
+    // Rehydrate the contexts on this end.
+    $contexts = [];
+    list($entity_type, $bundle) = explode('__', $machine_name);
+    $entity_definition = new ContextDefinition("entity:$entity_type", $this->t('Entity being panelized'));
+    $contexts['@panelizer.entity_context:entity'] = new Context($entity_definition);
+    $contexts = $contexts + \Drupal::service('ctools.context_mapper')->getContextValues($cached_values['contexts']);
+    $variant_plugin->setContexts($contexts);
+
+    // Add a section containing the available blocks to be added to the variant.
+    $build = [
+      '#type' => 'container',
+      '#attached' => [
+        'library' => [
+          'core/drupal.ajax',
+        ],
+      ],
+    ];
+    $available_plugins = $this->blockManager->getDefinitionsForContexts($variant_plugin->getContexts());
+    // Order by category, and then by admin label.
+    $available_plugins = $this->blockManager->getSortedDefinitions($available_plugins);
+    foreach ($available_plugins as $plugin_id => $plugin_definition) {
+      // Make a section for each region.
+      $category = $plugin_definition['category'];
+      $category_key = 'category-' . $category;
+      if (!isset($build[$category_key])) {
+        $build[$category_key] = [
+          '#type' => 'fieldgroup',
+          '#title' => $category,
+          'content' => [
+            '#theme' => 'links',
+          ],
+        ];
+      }
+      // Add a link for each available block within each region.
+      $build[$category_key]['content']['#links'][$plugin_id] = [
+        'title' => $plugin_definition['admin_label'],
+        'url' => Url::fromRoute('panelizer.wizard.step.content.add_block', [
+          'machine_name' => $machine_name,
+          'block_id' => $plugin_id,
+          'region' => $request->query->get('region'),
+          'destination' => $request->query->get('destination'),
+        ]),
+        'attributes' => $this->getAjaxAttributes(),
+      ];
+    }
+    return $build;
+  }
+
+}
diff --git a/src/Form/PanelizerDefaultDelete.php b/src/Form/PanelizerDefaultDelete.php
new file mode 100644
index 0000000..f19b881
--- /dev/null
+++ b/src/Form/PanelizerDefaultDelete.php
@@ -0,0 +1,91 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\panelizer\Form\PanelizerDefaultDelete.
+ */
+
+namespace Drupal\panelizer\Form;
+
+use Drupal\Core\Form\ConfirmFormBase;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Url;
+
+class PanelizerDefaultDelete extends ConfirmFormBase {
+
+  /**
+   * @var string
+   * The type of entity.
+   */
+  protected $entity_type_id;
+
+  /**
+   * @var string
+   * The entity bundle.
+   */
+  protected $bundle;
+
+  /**
+   * @var string
+   * The view mode.
+   */
+  protected $view_mode;
+
+  /**
+   * @var string
+   * The panelizer default display id.
+   */
+  protected $display_id;
+
+  public function getQuestion() {
+    return 'Are you certain you want to delete this panelizer default?.';
+  }
+
+  public function getCancelUrl() {
+    $bundle_entity_type = \Drupal::entityTypeManager()->getDefinition($this->entity_type_id)->getBundleEntityType();
+    if ($this->view_mode == 'default') {
+      $route = "entity.entity_view_display.{$this->entity_type_id}.default";
+      $arguments = [
+        $bundle_entity_type => $this->bundle,
+      ];
+    }
+    else {
+      $route = "entity.entity_view_display.{$this->entity_type_id}.view_mode";
+      $arguments = [
+        $bundle_entity_type => $this->bundle,
+        'view_mode_name' => $this->view_mode,
+      ];
+    }
+    return new Url($route, $arguments);
+  }
+
+  public function getFormId() {
+    return 'panelizer_default_delete';
+  }
+
+  public function buildForm(array $form, FormStateInterface $form_state, $machine_name = NULL) {
+    list($entity_type_id, $bundle, $view_mode, $display_id) = explode('__', $machine_name);
+    $this->entity_type_id = $entity_type_id;
+    $this->bundle = $bundle;
+    $this->view_mode = $view_mode;
+    $this->display_id = $display_id;
+    return parent::buildForm($form, $form_state); // TODO: Change the autogenerated stub
+  }
+
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    /** @var \Drupal\panelizer\Panelizer $panelizer */
+    $panelizer = \Drupal::service('panelizer');
+    /** @var \Drupal\panels\PanelsDisplayManagerInterface $display_manager */
+    $display_manager = \Drupal::service('panels.display_manager');
+    $display = $panelizer->getEntityViewDisplay($this->entity_type_id, $this->bundle, $this->view_mode);
+    $displays = $panelizer->getDefaultPanelsDisplays($this->entity_type_id, $this->bundle, $this->view_mode, $display);
+    unset($displays[$this->display_id]);
+    foreach ($displays as $key => $value) {
+      $displays[$key] = $display_manager->exportDisplay($value);
+    }
+    $display->setThirdPartySetting('panelizer', 'displays', $displays);
+    $display->save();
+    $form_state->setRedirectUrl($this->getCancelUrl());
+  }
+
+}
\ No newline at end of file
diff --git a/src/Form/PanelizerDefaultSelect.php b/src/Form/PanelizerDefaultSelect.php
new file mode 100644
index 0000000..ee21310
--- /dev/null
+++ b/src/Form/PanelizerDefaultSelect.php
@@ -0,0 +1,84 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\panelizer\Form\PanelizerDefaultSelect.
+ */
+
+namespace Drupal\panelizer\Form;
+
+use Drupal\Core\Form\ConfirmFormBase;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Url;
+
+class PanelizerDefaultSelect extends ConfirmFormBase {
+  /**
+   * @var string
+   * The type of entity.
+   */
+  protected $entity_type_id;
+
+  /**
+   * @var string
+   * The entity bundle.
+   */
+  protected $bundle;
+
+  /**
+   * @var string
+   * The view mode.
+   */
+  protected $view_mode;
+
+  /**
+   * @var string
+   * The panelizer default display id.
+   */
+  protected $display_id;
+
+  public function getQuestion() {
+    return 'Are you certain you want to set this panelizer default as the default for this bundle?.';
+  }
+
+  public function getCancelUrl() {
+    $bundle_entity_type = \Drupal::entityTypeManager()->getDefinition($this->entity_type_id)->getBundleEntityType();
+    if ($this->view_mode == 'default') {
+      $route = "entity.entity_view_display.{$this->entity_type_id}.default";
+      $arguments = [
+        $bundle_entity_type => $this->bundle,
+      ];
+    }
+    else {
+      $route = "entity.entity_view_display.{$this->entity_type_id}.view_mode";
+      $arguments = [
+        $bundle_entity_type => $this->bundle,
+        'view_mode_name' => $this->view_mode,
+      ];
+    }
+    return new Url($route, $arguments);
+  }
+
+  public function getFormId() {
+    return 'panelizer_default_delete';
+  }
+
+  public function buildForm(array $form, FormStateInterface $form_state, $machine_name = NULL) {
+    list($entity_type_id, $bundle, $view_mode, $display_id) = explode('__', $machine_name);
+    $this->entity_type_id = $entity_type_id;
+    $this->bundle = $bundle;
+    $this->view_mode = $view_mode;
+    $this->display_id = $display_id;
+    return parent::buildForm($form, $form_state); // TODO: Change the autogenerated stub
+  }
+
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    /** @var \Drupal\panelizer\Panelizer $panelizer */
+    $panelizer = \Drupal::service('panelizer');
+    $display = $panelizer->getEntityViewDisplay($this->entity_type_id, $this->bundle, $this->view_mode);
+    $settings = $panelizer->getPanelizerSettings($this->entity_type_id, $this->bundle, $this->view_mode, $display);
+    $settings['default'] = $this->display_id;
+    $panelizer->setPanelizerSettings($this->entity_type_id, $this->bundle, $this->view_mode, $settings, $display);
+    $form_state->setRedirectUrl($this->getCancelUrl());
+  }
+
+}
diff --git a/src/Form/PanelizerWizardAddBlockForm.php b/src/Form/PanelizerWizardAddBlockForm.php
new file mode 100644
index 0000000..0cf818b
--- /dev/null
+++ b/src/Form/PanelizerWizardAddBlockForm.php
@@ -0,0 +1,81 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\panelizer\Form\PanelizerWizardAddBlockForm.
+ */
+
+namespace Drupal\panelizer\Form;
+
+use Drupal\Component\Plugin\PluginManagerInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\user\SharedTempStoreFactory;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Provides a form for adding a block plugin to a variant.
+ */
+class PanelizerWizardAddBlockForm extends PanelizerWizardConfigureBlockFormBase   {
+
+  /**
+   * The block manager.
+   *
+   * @var \Drupal\Component\Plugin\PluginManagerInterface
+   */
+  protected $blockManager;
+
+  /**
+   * Constructs a new VariantPluginFormBase.
+   *
+   * @param \Drupal\Component\Plugin\PluginManagerInterface $block_manager
+   *   The block manager.
+   */
+  public function __construct(SharedTempStoreFactory $tempstore, PluginManagerInterface $block_manager) {
+    parent::__construct($tempstore);
+    $this->blockManager = $block_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('user.shared_tempstore'),
+      $container->get('plugin.manager.block')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'panelizer_wizard_add_block_form';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function prepareBlock($plugin_id) {
+    $block = $this->blockManager->createInstance($plugin_id);
+    $block_id = $this->getVariantPlugin()->addBlock($block->getConfiguration());
+    return $this->getVariantPlugin()->getBlock($block_id);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state, Request $request = NULL, $machine_name = NULL, $block_id = NULL) {
+    $form = parent::buildForm($form, $form_state, $machine_name, $block_id);
+    $form['region']['#default_value'] = $request->query->get('region');
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function submitText() {
+    return $this->t('Add block');
+  }
+
+}
diff --git a/src/Form/PanelizerWizardConfigureBlockFormBase.php b/src/Form/PanelizerWizardConfigureBlockFormBase.php
new file mode 100644
index 0000000..da9fe73
--- /dev/null
+++ b/src/Form/PanelizerWizardConfigureBlockFormBase.php
@@ -0,0 +1,193 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\panelizer\Form\PanelizerWizardConfigureBlockFormBase.
+ */
+
+namespace Drupal\panelizer\Form;
+
+use Drupal\Core\Form\FormBase;
+use Drupal\Core\Form\FormState;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Plugin\Context\Context;
+use Drupal\Core\Plugin\Context\ContextDefinition;
+use Drupal\Core\Plugin\ContextAwarePluginAssignmentTrait;
+use Drupal\Core\Plugin\ContextAwarePluginInterface;
+use Drupal\user\SharedTempStoreFactory;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Provides a base form for configuring a block as part of a variant.
+ */
+abstract class PanelizerWizardConfigureBlockFormBase extends FormBase {
+
+  use ContextAwarePluginAssignmentTrait;
+
+  /**
+   * Tempstore factory.
+   *
+   * @var \Drupal\user\SharedTempStoreFactory
+   */
+  protected $tempstore;
+
+  /**
+   * The variant plugin.
+   *
+   * @var \Drupal\page_manager\Plugin\DisplayVariant\PageBlockDisplayVariant
+   */
+  protected $variantPlugin;
+
+  /**
+   * The plugin being configured.
+   *
+   * @var \Drupal\Core\Block\BlockPluginInterface
+   */
+  protected $block;
+
+  /**
+   * Constructs a new VariantPluginConfigureBlockFormBase.
+   *
+   * @param \Drupal\user\SharedTempStoreFactory $tempstore
+   *   The tempstore factory.
+   */
+  public function __construct(SharedTempStoreFactory $tempstore) {
+    $this->tempstore = $tempstore;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('user.shared_tempstore')
+    );
+  }
+
+  /**
+   * Get the tempstore id.
+   *
+   * @return string
+   */
+  protected function getTempstoreId() {
+    return 'panelizer.wizard';
+  }
+
+  /**
+   * Get the tempstore.
+   *
+   * @return \Drupal\user\SharedTempStore
+   */
+  protected function getTempstore() {
+    return $this->tempstore->get($this->getTempstoreId());
+  }
+
+  /**
+   * Prepares the block plugin based on the block ID.
+   *
+   * @param string $block_id
+   *   Either a block ID, or the plugin ID used to create a new block.
+   *
+   * @return \Drupal\Core\Block\BlockPluginInterface
+   *   The block plugin.
+   */
+  abstract protected function prepareBlock($block_id);
+
+  /**
+   * Returns the text to use for the submit button.
+   *
+   * @return string
+   *   The submit button text.
+   */
+  abstract protected function submitText();
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state, $machine_name = NULL, $block_id = NULL) {
+    $cached_values = $this->tempstore->get($this->getTempstoreId())->get($machine_name);
+    /** @var \Drupal\page_manager\Plugin\DisplayVariant\PageBlockDisplayVariant $variant_plugin */
+    $this->variantPlugin = $cached_values['plugin'];
+
+    // Rehydrate the contexts on this end.
+    $contexts = [];
+    list($entity_type, $bundle) = explode('__', $machine_name);
+    $entity_definition = new ContextDefinition("entity:$entity_type", $this->t('Entity being panelized'));
+    $contexts['@panelizer.entity_context:entity'] = new Context($entity_definition);
+    $contexts = $contexts + \Drupal::service('ctools.context_mapper')->getContextValues($cached_values['contexts']);
+    $this->variantPlugin->setContexts($contexts);
+    $form_state->setTemporaryValue('gathered_contexts', $contexts);
+
+    $this->block = $this->prepareBlock($block_id);
+    $form_state->set('machine_name', $machine_name);
+    $form_state->set('block_id', $this->block->getConfiguration()['uuid']);
+
+    $form['#tree'] = TRUE;
+    $form['settings'] = $this->block->buildConfigurationForm([], $form_state);
+    $form['settings']['id'] = [
+      '#type' => 'value',
+      '#value' => $this->block->getPluginId(),
+    ];
+    $form['region'] = [
+      '#title' => $this->t('Region'),
+      '#type' => 'select',
+      '#options' => $this->getVariantPlugin()->getRegionNames(),
+      '#default_value' => $this->getVariantPlugin()->getRegionAssignment($this->block->getConfiguration()['uuid']),
+      '#required' => TRUE,
+    ];
+
+    $form['actions']['submit'] = [
+      '#type' => 'submit',
+      '#value' => $this->submitText(),
+      '#button_type' => 'primary',
+    ];
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateForm(array &$form, FormStateInterface $form_state) {
+    // The page might have been serialized, resulting in a new variant
+    // collection. Refresh the block object.
+    $this->block = $this->getVariantPlugin()->getBlock($form_state->get('block_id'));
+
+    $settings = (new FormState())->setValues($form_state->getValue('settings'));
+    // Call the plugin validate handler.
+    $this->block->validateConfigurationForm($form, $settings);
+    // Update the original form values.
+    $form_state->setValue('settings', $settings->getValues());
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    $settings = (new FormState())->setValues($form_state->getValue('settings'));
+
+    // Call the plugin submit handler.
+    $this->block->submitConfigurationForm($form, $settings);
+    // Update the original form values.
+    $form_state->setValue('settings', $settings->getValues());
+
+    if ($this->block instanceof ContextAwarePluginInterface) {
+      $this->block->setContextMapping($settings->getValue('context_mapping', []));
+    }
+
+    $this->getVariantPlugin()->updateBlock($this->block->getConfiguration()['uuid'], ['region' => $form_state->getValue('region')]);
+
+    $cached_values = $this->getTempstore()->get($form_state->get('machine_name'));
+    $cached_values['plugin'] = $this->getVariantPlugin();
+    $this->getTempstore()->set($form_state->get('machine_name'), $cached_values);
+  }
+
+  /**
+   * Gets the variant plugin for this page variant entity.
+   *
+   * @return \Drupal\ctools\Plugin\BlockVariantInterface
+   */
+  protected function getVariantPlugin() {
+    return $this->variantPlugin;
+  }
+
+}
diff --git a/src/Form/PanelizerWizardContentForm.php b/src/Form/PanelizerWizardContentForm.php
new file mode 100644
index 0000000..8ef8610
--- /dev/null
+++ b/src/Form/PanelizerWizardContentForm.php
@@ -0,0 +1,242 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\panelizer\Form\PanelizerWizardContentForm.
+ */
+
+namespace Drupal\panelizer\Form;
+
+use Drupal\Core\Form\FormBase;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Url;
+use Drupal\ctools\Form\AjaxFormTrait;
+use Drupal\user\SharedTempStoreFactory;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Provides a form for editing a variant's content.
+ */
+class PanelizerWizardContentForm extends FormBase {
+
+  use AjaxFormTrait;
+
+  /**
+   * Tempstore factory.
+   *
+   * @var \Drupal\user\SharedTempStoreFactory
+   */
+  protected $tempstore;
+
+  /**
+   * Constructs a new VariantPluginContentForm.
+   *
+   * @param \Drupal\user\SharedTempStoreFactory $tempstore
+   *   The tempstore factory.
+   */
+  public function __construct(SharedTempStoreFactory $tempstore) {
+    $this->tempstore = $tempstore;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('user.shared_tempstore')
+    );
+  }
+
+  /**
+   * Get the tempstore ID.
+   *
+   * @return string
+   */
+  protected function getTempstoreId() {
+    return 'panelizer.wizard';
+  }
+
+  /**
+   * Get the tempstore.
+   *
+   * @return \Drupal\user\SharedTempStore
+   */
+  protected function getTempstore() {
+    return $this->tempstore->get($this->getTempstoreId());
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'panelizer_wizard_block_page_content';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state) {
+    $cached_values = $form_state->getTemporaryValue('wizard');
+    /** @var \Drupal\page_manager\Plugin\DisplayVariant\PageBlockDisplayVariant $variant_plugin */
+    $variant_plugin = $cached_values['plugin'];
+
+    // Set up the attributes used by a modal to prevent duplication later.
+    $attributes = $this->getAjaxAttributes();
+    $add_button_attributes = $this->getAjaxButtonAttributes();
+
+    if ($block_assignments = $variant_plugin->getRegionAssignments()) {
+      // Build a table of all blocks used by this variant.
+      $form['add'] = [
+        '#type' => 'link',
+        '#title' => $this->t('Add new block'),
+        '#url' => Url::fromRoute('panelizer.wizard.step.content.select_block', [
+          'machine_name' => $cached_values['id'],
+          'destination' => $this->getRequest()->getRequestUri(),
+        ]),
+        '#attributes' => $add_button_attributes,
+        '#attached' => [
+          'library' => [
+            'core/drupal.ajax',
+          ],
+        ],
+      ];
+      $form['blocks'] = [
+        '#type' => 'table',
+        '#header' => [
+          $this->t('Label'),
+          $this->t('Plugin ID'),
+          $this->t('Region'),
+          $this->t('Weight'),
+          $this->t('Operations'),
+        ],
+        '#empty' => $this->t('There are no regions for blocks.'),
+      ];
+      // Loop through the blocks per region.
+      foreach ($block_assignments as $region => $blocks) {
+        // Add a section for each region and allow blocks to be dragged between
+        // them.
+        $form['blocks']['#tabledrag'][] = [
+          'action' => 'match',
+          'relationship' => 'sibling',
+          'group' => 'block-region-select',
+          'subgroup' => 'block-region-' . $region,
+          'hidden' => FALSE,
+        ];
+        $form['blocks']['#tabledrag'][] = [
+          'action' => 'order',
+          'relationship' => 'sibling',
+          'group' => 'block-weight',
+          'subgroup' => 'block-weight-' . $region,
+        ];
+        $form['blocks'][$region] = [
+          '#attributes' => [
+            'class' => ['region-title', 'region-title-' . $region],
+            'no_striping' => TRUE,
+          ],
+        ];
+        $form['blocks'][$region]['title'] = [
+          '#markup' => $variant_plugin->getRegionName($region),
+          '#wrapper_attributes' => [
+            'colspan' => 5,
+          ],
+        ];
+        $form['blocks'][$region . '-message'] = [
+          '#attributes' => [
+            'class' => [
+              'region-message',
+              'region-' . $region . '-message',
+              empty($blocks) ? 'region-empty' : 'region-populated',
+            ],
+          ],
+        ];
+        $form['blocks'][$region . '-message']['message'] = [
+          '#markup' => '<em>' . $this->t('No blocks in this region') . '</em>',
+          '#wrapper_attributes' => [
+            'colspan' => 5,
+          ],
+        ];
+
+        /** @var \Drupal\Core\Block\BlockPluginInterface[] $blocks */
+        foreach ($blocks as $block_id => $block) {
+          $row = [
+            '#attributes' => [
+              'class' => ['draggable'],
+            ],
+          ];
+          $row['label']['#markup'] = $block->label();
+          $row['id']['#markup'] = $block->getPluginId();
+          // Allow the region to be changed for each block.
+          $row['region'] = [
+            '#title' => $this->t('Region'),
+            '#title_display' => 'invisible',
+            '#type' => 'select',
+            '#options' => $variant_plugin->getRegionNames(),
+            '#default_value' => $variant_plugin->getRegionAssignment($block_id),
+            '#attributes' => [
+              'class' => ['block-region-select', 'block-region-' . $region],
+            ],
+          ];
+          // Allow the weight to be changed for each block.
+          $configuration = $block->getConfiguration();
+          $row['weight'] = [
+            '#type' => 'weight',
+            '#default_value' => isset($configuration['weight']) ? $configuration['weight'] : 0,
+            '#title' => $this->t('Weight for @block block', ['@block' => $block->label()]),
+            '#title_display' => 'invisible',
+            '#attributes' => [
+              'class' => ['block-weight', 'block-weight-' . $region],
+            ],
+          ];
+          // Add the operation links.
+          $operations = [];
+          $operations['edit'] = [
+            'title' => $this->t('Edit'),
+            'url' => Url::fromRoute('panelizer.wizard.step.content.edit_block', [
+              'machine_name' => $cached_values['id'],
+              'block_id' => $block_id,
+              'destination' => $this->getRequest()->getRequestUri(),
+            ]),
+            'attributes' => $attributes,
+          ];
+          $operations['delete'] = [
+            'title' => $this->t('Delete'),
+            'url' => Url::fromRoute('panelizer.wizard.step.content.delete_block', [
+              'machine_name' => $cached_values['id'],
+              'block_id' => $block_id,
+              'destination' => $this->getRequest()->getRequestUri(),
+            ]),
+            'attributes' => $attributes,
+          ];
+
+          $row['operations'] = [
+            '#type' => 'operations',
+            '#links' => $operations,
+          ];
+          $form['blocks'][$block_id] = $row;
+        }
+      }
+    }
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    $cached_values = $form_state->getTemporaryValue('wizard');
+    /** @var \Drupal\page_manager\Plugin\DisplayVariant\PageBlockDisplayVariant $variant_plugin */
+    $variant_plugin = $cached_values['plugin'];
+
+    // If the blocks were rearranged, update their values.
+    if (!$form_state->isValueEmpty('blocks')) {
+      foreach ($form_state->getValue('blocks') as $block_id => $block_values) {
+        $variant_plugin->updateBlock($block_id, $block_values);
+      }
+    }
+
+    // Remove from the tempstore so we refresh from the database the next time
+    // we come here.
+    $this->getTempstore()->delete($variant_plugin->id());
+  }
+
+}
diff --git a/src/Form/PanelizerWizardContextConfigure.php b/src/Form/PanelizerWizardContextConfigure.php
new file mode 100644
index 0000000..aaa26c4
--- /dev/null
+++ b/src/Form/PanelizerWizardContextConfigure.php
@@ -0,0 +1,61 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\panelizer\Form\PanelizerWizardContextConfigure.
+ */
+
+namespace Drupal\panelizer\Form;
+
+use Drupal\Core\Plugin\Context\ContextInterface;
+use Drupal\ctools\Form\ContextConfigure;
+
+class PanelizerWizardContextConfigure extends ContextConfigure {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getParentRouteInfo($cached_values) {
+    return ['panelizer.wizard.add.step', [
+      'machine_name' => $cached_values['id'],
+      'step' => 'contexts',
+    ]];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getContexts($cached_values) {
+    $static_contexts = isset($cached_values['contexts']) ? $cached_values['contexts'] : [];
+    $static_contexts = \Drupal::service('ctools.context_mapper')->getContextValues($static_contexts);
+    return $static_contexts;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function addContext($cached_values, $context_id, ContextInterface $context) {
+    $cached_values['contexts'][$context_id] = [
+      'label' => $context->getContextDefinition()->getLabel(),
+      'type' => $context->getContextDefinition()->getDataType(),
+      'description' => $context->getContextDefinition()->getDescription(),
+      'value' => strpos($context->getContextDefinition()->getDataType(), 'entity:') === 0 ? $context->getContextValue()->uuid() : $context->getContextValue(),
+    ];
+    return $cached_values;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function contextExists($value, $element, $form_state) {
+    return FALSE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function disableMachineName($cached_values, $machine_name) {
+    return !empty($cached_values['contexts'][$machine_name]);
+  }
+
+}
diff --git a/src/Form/PanelizerWizardContextDeleteForm.php b/src/Form/PanelizerWizardContextDeleteForm.php
new file mode 100644
index 0000000..84c6404
--- /dev/null
+++ b/src/Form/PanelizerWizardContextDeleteForm.php
@@ -0,0 +1,59 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\panelizer\Form\PanelizerWizardContextDeleteForm.
+ */
+
+namespace Drupal\panelizer\Form;
+
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Url;
+use Drupal\ctools\Form\ContextDelete;
+
+/**
+ * Provides a form for deleting a context.
+ */
+class PanelizerWizardContextDeleteForm extends ContextDelete {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'panelizer_wizard_context_delete_form';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getQuestion() {
+    $cached_values = $this->getTempstore();
+    $context = $cached_values['contexts'][$this->context_id];
+    return $this->t('Are you sure you want to delete the context @label?', ['@label' => $context['label']]);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCancelUrl() {
+    $cached_values = $this->getTempstore();
+
+    return new Url('panelizer.wizard.add.step', [
+      'machine_name' => $cached_values['id'],
+      'step' => 'contexts',
+    ]);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    $cached_values = $this->getTempstore();
+    $context = $cached_values['contexts'][$this->context_id];
+    drupal_set_message($this->t('The static context %label has been removed.', ['%label' => $context['label']]));
+    unset($cached_values['contexts'][$this->context_id]);
+    $this->setTempstore($cached_values);
+    parent::submitForm($form, $form_state);
+  }
+
+}
diff --git a/src/Form/PanelizerWizardContextForm.php b/src/Form/PanelizerWizardContextForm.php
new file mode 100644
index 0000000..c7ef4c7
--- /dev/null
+++ b/src/Form/PanelizerWizardContextForm.php
@@ -0,0 +1,130 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\panelizer\Form\PanelizerWizardContextForm.
+ */
+
+namespace Drupal\panelizer\Form;
+
+use Drupal\Core\Ajax\AjaxResponse;
+use Drupal\Core\Ajax\OpenModalDialogCommand;
+use Drupal\Core\Form\FormBuilderInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Plugin\Context\Context;
+use Drupal\Core\Plugin\Context\ContextDefinition;
+use Drupal\ctools\Form\ManageContext;
+
+/**
+ * Simple wizard step form.
+ */
+class PanelizerWizardContextForm extends ManageContext {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $relationships = FALSE;
+
+  /**
+   * Returns a unique string identifying the form.
+   *
+   * @return string
+   *   The unique string identifying the form.
+   */
+  public function getFormId() {
+    return 'panelizer_wizard_context_form';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getContextClass($cached_values) {
+    return PanelizerWizardContextConfigure::class;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getRelationshipClass($cached_values) {}
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getContextAddRoute($cached_values) {
+    return 'panelizer.wizard.step.context.add';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getRelationshipAddRoute($cached_values) {
+    return 'panelizer.wizard.step.context.add';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getContexts($cached_values) {
+    list($entity_type, $bundle) = explode('__', $cached_values['id']);
+    $contexts = [];
+    $entity_definition = \Drupal::service('entity_type.manager')->getDefinition($entity_type);
+    $base_context_definition = new ContextDefinition("entity:$entity_type", $this->t('@entity being panelized', ['@entity' => $entity_definition->getLabel()]));
+    $base_context_definition->addConstraint('Bundle', [$bundle => $bundle]);
+    $contexts['@panelizer.entity_context:entity'] = new Context($base_context_definition);
+    $static_contexts = !empty($cached_values['contexts']) ? $cached_values['contexts'] : [];
+    $static_contexts = \Drupal::service('ctools.context_mapper')->getContextValues($static_contexts);
+    $contexts = $contexts + $static_contexts;
+    return $contexts;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getTempstoreId() {
+    return 'panelizer.wizard';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getContextOperationsRouteInfo($cached_values, $machine_name, $row) {
+    return ['panelizer.wizard.step.context', [
+      'machine_name' => $machine_name,
+      'context_id' => $row,
+    ]];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getRelationshipOperationsRouteInfo($cached_values, $machine_name, $row) {
+    return ['panelizer.wizard.step.context', [
+      'machine_name' => $machine_name,
+      'context_id' => $row,
+    ]];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function isEditableContext($cached_values, $row) {
+    $context = $cached_values['contexts'][$row];
+    return !empty($context['value']);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function addContext(array &$form, FormStateInterface $form_state) {
+    $cached_values = $form_state->getTemporaryValue('wizard');
+    $context = $form_state->getValue('context');
+    $content = $this->formBuilder->getForm($this->getContextClass($cached_values), $context, $this->getTempstoreId(), $this->machine_name);
+    $content['#attached']['library'][] = 'core/drupal.dialog.ajax';
+    list(, $route_parameters) = $this->getContextOperationsRouteInfo($cached_values, $this->machine_name, $context);
+    $content['submit']['#attached']['drupalSettings']['ajax'][$content['submit']['#id']]['url'] = $this->url($this->getContextAddRoute($cached_values), $route_parameters, ['query' => [FormBuilderInterface::AJAX_FORM_REQUEST => TRUE]]);
+    $response = new AjaxResponse();
+    $response->addCommand(new OpenModalDialogCommand($this->t('Add new context'), $content, array('width' => '700')));
+    return $response;
+  }
+
+}
diff --git a/src/Form/PanelizerWizardDeleteBlockForm.php b/src/Form/PanelizerWizardDeleteBlockForm.php
new file mode 100644
index 0000000..ef78dc7
--- /dev/null
+++ b/src/Form/PanelizerWizardDeleteBlockForm.php
@@ -0,0 +1,100 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\panelizer\Form\PanelizerWizardDeleteBlockForm.
+ */
+
+namespace Drupal\panelizer\Form;
+
+use Drupal\Core\Form\ConfirmFormBase;
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Provides a form for deleting an access condition.
+ */
+class PanelizerWizardDeleteBlockForm extends ConfirmFormBase {
+
+  /**
+   * @var \Drupal\ctools\Plugin\BlockVariantInterface
+   */
+  protected $plugin;
+
+  /**
+   * The plugin being configured.
+   *
+   * @var \Drupal\Core\Block\BlockPluginInterface
+   */
+  protected $block;
+
+  /**
+   * Get the tempstore id.
+   *
+   * @return string
+   */
+  protected function getTempstoreId() {
+    return 'panelizer.wizard';
+  }
+
+  /**
+   * Get the tempstore.
+   *
+   * @return \Drupal\user\SharedTempStore
+   */
+  protected function getTempstore() {
+    return \Drupal::service('user.shared_tempstore')->get($this->getTempstoreId());
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'panelizer_wizard_delete_block_form';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getQuestion() {
+    return $this->t('Are you sure you want to delete the block %label?', ['%label' => $this->block->label()]);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCancelUrl() {
+    return \Drupal::request()->attributes->get('destination');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getConfirmText() {
+    return $this->t('Delete');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state, $machine_name = NULL, $block_id = NULL) {
+    $this->plugin = $this->getTempstore()->get($machine_name)['plugin'];
+    $this->block = $this->plugin->getBlock($block_id);
+    $form['block_display'] = [
+      '#type' => 'value',
+      '#value' => $machine_name
+    ];
+    return parent::buildForm($form, $form_state);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    $this->plugin->removeBlock($this->block->getConfiguration()['uuid']);
+    $cached_values = $this->getTempstore()->get($form_state->getValue('block_display'));
+    $cached_values['plugin'] = $this->plugin;
+    $this->getTempstore()->set($form_state->getValue('block_display'), $cached_values);
+    drupal_set_message($this->t('The block %label has been removed.', ['%label' => $this->block->label()]));
+  }
+
+}
diff --git a/src/Form/PanelizerWizardEditBlockForm.php b/src/Form/PanelizerWizardEditBlockForm.php
new file mode 100644
index 0000000..ddf9366
--- /dev/null
+++ b/src/Form/PanelizerWizardEditBlockForm.php
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\panelizer\Form\PanelizerWizardEditBlockForm.
+ */
+
+namespace Drupal\panelizer\Form;
+
+/**
+ * Provides a form for editing a block plugin of a variant.
+ */
+class PanelizerWizardEditBlockForm extends PanelizerWizardConfigureBlockFormBase  {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'panelizer_wizard_edit_block_form';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function prepareBlock($block_id) {
+    return $this->getVariantPlugin()->getBlock($block_id);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function submitText() {
+    return $this->t('Update block');
+  }
+
+}
diff --git a/src/Form/PanelizerWizardGeneralForm.php b/src/Form/PanelizerWizardGeneralForm.php
new file mode 100644
index 0000000..2ab513a
--- /dev/null
+++ b/src/Form/PanelizerWizardGeneralForm.php
@@ -0,0 +1,69 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\panelizer\Form\PanelizerWizardGeneralForm.
+ */
+
+namespace Drupal\panelizer\Form;
+
+
+use Drupal\Core\Form\FormBase;
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * General settings for a panelized bundle.
+ */
+class PanelizerWizardGeneralForm extends FormBase {
+
+  /**
+   * The SharedTempStore key for our current wizard values.
+   *
+   * @var string|NULL
+   */
+  protected $machine_name;
+
+  /**
+   * Returns a unique string identifying the form.
+   *
+   * @return string
+   *   The unique string identifying the form.
+   */
+  public function getFormId() {
+    return 'panelizer_wizard_general_form';
+  }
+
+  /**
+   * Form constructor.
+   *
+   * @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.
+   *
+   * @param null|string $machine_name
+   *
+   * @return array The form structure.
+   * The form structure.
+   */
+  public function buildForm(array $form, FormStateInterface $form_state, $machine_name = NULL) {
+    $this->machine_name = $machine_name;
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateForm(array &$form, FormStateInterface $form_state) {
+    if ($form_state->hasValue('id') && !isset($this->machine_name) && $form_state->has('machine_name_prefix')) {
+      $form_state->setValue('id', "{$form_state->get('machine_name_prefix')}__{$form_state->getValue('id')}");
+    }
+  }
+
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {}
+
+}
diff --git a/src/Menu/AddDefaultLocalAction.php b/src/Menu/AddDefaultLocalAction.php
new file mode 100644
index 0000000..72467bb
--- /dev/null
+++ b/src/Menu/AddDefaultLocalAction.php
@@ -0,0 +1,22 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\panelizer\Menu\AddDefaultLocalAction.
+ */
+
+namespace Drupal\panelizer\Menu;
+
+use Drupal\Core\Menu\LocalActionDefault;
+use Drupal\Core\Routing\RouteMatchInterface;
+
+class AddDefaultLocalAction extends LocalActionDefault {
+
+  public function getRouteParameters(RouteMatchInterface $route_match) {
+    $this->pluginDefinition['route_parameters']['entity_type_id'] = $route_match->getCurrentRouteMatch()->getParameter('entity_type_id');
+    $this->pluginDefinition['route_parameters']['bundle'] = $route_match->getCurrentRouteMatch()->getParameter('bundle');
+    $this->pluginDefinition['route_parameters']['view_mode_name'] = $route_match->getCurrentRouteMatch()->getParameter('view_mode_name');
+    return parent::getRouteParameters($route_match);
+  }
+
+}
diff --git a/src/Panelizer.php b/src/Panelizer.php
index 864191d..8c3cf75 100644
--- a/src/Panelizer.php
+++ b/src/Panelizer.php
@@ -20,6 +20,7 @@ use Drupal\Core\Session\AccountInterface;
 use Drupal\Core\Session\AccountProxyInterface;
 use Drupal\Core\StringTranslation\StringTranslationTrait;
 use Drupal\Core\StringTranslation\TranslationInterface;
+use Drupal\ctools\ContextMapperInterface;
 use Drupal\panelizer\Exception\PanelizerException;
 use Drupal\panelizer\Plugin\PanelizerEntityManager;
 use Drupal\panels\PanelsDisplayManagerInterface;
@@ -89,6 +90,13 @@ class Panelizer implements PanelizerInterface {
   protected $panelsManager;
 
   /**
+   * The context mapper.
+   *
+   * @var \Drupal\ctools\ContextMapperInterface
+   */
+  protected $contextMapper;
+
+  /**
    * Constructs a Panelizer.
    *
    * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
@@ -109,8 +117,10 @@ class Panelizer implements PanelizerInterface {
    *   The Panels display manager.
    * @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
    *   The string translation service.
+   * @param \Drupal\ctools\ContextMapperInterface $context_mapper
+   *   The context mapper service.
    */
-  public function __construct(EntityTypeManagerInterface $entity_type_manager, EntityTypeBundleInfoInterface $entity_type_bundle_info, EntityFieldManagerInterface $entity_field_manager, FieldTypePluginManagerInterface $field_type_manager, ModuleHandlerInterface $module_handler, AccountProxyInterface $current_user, PanelizerEntityManager $panelizer_entity_manager, PanelsDisplayManagerInterface $panels_manager, TranslationInterface $string_translation) {
+  public function __construct(EntityTypeManagerInterface $entity_type_manager, EntityTypeBundleInfoInterface $entity_type_bundle_info, EntityFieldManagerInterface $entity_field_manager, FieldTypePluginManagerInterface $field_type_manager, ModuleHandlerInterface $module_handler, AccountProxyInterface $current_user, PanelizerEntityManager $panelizer_entity_manager, PanelsDisplayManagerInterface $panels_manager, TranslationInterface $string_translation, ContextMapperInterface $context_mapper) {
     $this->entityTypeManager = $entity_type_manager;
     $this->entityTypeBundleInfo = $entity_type_bundle_info;
     $this->entityFieldManager = $entity_field_manager;
@@ -120,6 +130,7 @@ class Panelizer implements PanelizerInterface {
     $this->panelizerEntityManager = $panelizer_entity_manager;
     $this->panelsManager = $panels_manager;
     $this->stringTranslation = $string_translation;
+    $this->contextMapper = $context_mapper;
   }
 
   /**
@@ -147,7 +158,7 @@ class Panelizer implements PanelizerInterface {
    * @return \Drupal\Core\Entity\Display\EntityViewDisplayInterface|NULL
    *   The entity view display if one exists; NULL otherwise.
    */
-  protected function getEntityViewDisplay($entity_type_id, $bundle, $view_mode) {
+  public function getEntityViewDisplay($entity_type_id, $bundle, $view_mode) {
     // Check the existence and status of:
     // - the display for the view mode,
     // - the 'default' display.
@@ -200,7 +211,7 @@ class Panelizer implements PanelizerInterface {
    */
   public function getPanelsDisplay(FieldableEntityInterface $entity, $view_mode, EntityViewDisplayInterface $display = NULL) {
     $settings = $this->getPanelizerSettings($entity->getEntityTypeId(), $entity->bundle(), $view_mode, $display);
-    if ($settings['custom'] && isset($entity->panelizer)) {
+    if ($settings['custom'] && isset($entity->panelizer) && $entity->panelizer->first()) {
       /** @var \Drupal\Core\Field\FieldItemInterface[] $values */
       $values = [];
       foreach ($entity->panelizer as $item) {
@@ -217,7 +228,7 @@ class Panelizer implements PanelizerInterface {
       }
     }
 
-    return $this->getDefaultPanelsDisplay('default', $entity->getEntityTypeId(), $entity->bundle(), $view_mode, $display);
+    return $this->getDefaultPanelsDisplay($settings['default'], $entity->getEntityTypeId(), $entity->bundle(), $view_mode, $display);
   }
 
   /**
@@ -303,6 +314,8 @@ class Panelizer implements PanelizerInterface {
 
     $config = $display->getThirdPartySetting('panelizer', 'displays', []);
     if (!empty($config[$name])) {
+      // Set a default just in case.
+      $config[$name]['builder'] = empty($config[$name]['builder']) ? 'standard' : $config[$name]['builder'];
       // @todo: validate schema after https://www.drupal.org/node/2392057 is fixed.
       $panels_display = $this->panelsManager->importDisplay($config[$name], FALSE);
     }
@@ -339,6 +352,37 @@ class Panelizer implements PanelizerInterface {
     $display->save();
   }
 
+  public function getDisplayStaticContexts($name, $entity_type_id, $bundle, $view_mode, EntityViewDisplayInterface $display = NULL) {
+    if (!$display) {
+      $display = $this->getEntityViewDisplay($entity_type_id, $bundle, $view_mode);
+      // If we still don't find a display, then we won't find a Panelizer
+      // default for sure.
+      if (!$display) {
+        return NULL;
+      }
+    }
+
+    $config = $display->getThirdPartySetting('panelizer', 'displays', []);
+    if (!empty($config[$name]) && !empty($config[$name]['static_context'])) {
+      return $this->contextMapper->getContextValues($config[$name]['static_context']);
+    }
+    return [];
+  }
+
+  public function setDisplayStaticContexts($name, $entity_type_id, $bundle, $view_mode, $contexts) {
+    $display = $this->getEntityViewDisplay($entity_type_id, $bundle, $view_mode);
+    if (!$display) {
+      throw new PanelizerException("Unable to find display for given entity type, bundle and view mode");
+    }
+
+    // Set this Panels display's static contexts.
+    $panels_displays = $display->getThirdPartySetting('panelizer', 'displays', []);
+    $panels_displays[$name]['static_context'] = $contexts;
+    $display->setThirdPartySetting('panelizer', 'displays', $panels_displays);
+
+    $display->save();
+  }
+
   /**
    * {@inheritdoc}
    */
@@ -365,6 +409,7 @@ class Panelizer implements PanelizerInterface {
     $settings = [
       'enable' => $this->isPanelized($entity_type_id, $bundle, $view_mode, $display),
       'custom' => $display->getThirdPartySetting('panelizer', 'custom', FALSE),
+      'default' => $display->getThirdPartySetting('panelizer', 'default', 'default'),
     ];
 
     // Make sure that the Panelizer field actually exists.
@@ -386,6 +431,7 @@ class Panelizer implements PanelizerInterface {
 
     $display->setThirdPartySetting('panelizer', 'enable', !empty($settings['enable']));
     $display->setThirdPartySetting('panelizer', 'custom', !empty($settings['enable']) && !empty($settings['custom']));
+    $display->setThirdPartySetting('panelizer', 'default', $settings['default']);
 
     if (!empty($settings['enable'])) {
       // Set the default display.
@@ -394,6 +440,7 @@ class Panelizer implements PanelizerInterface {
         /** @var \Drupal\panelizer\Plugin\PanelizerEntityInterface $panelizer_entity_plugin */
         $panelizer_entity_plugin = $this->panelizerEntityManager->createInstance($display->getTargetEntityTypeId(), []);
         $displays['default'] = $this->panelsManager->exportDisplay($panelizer_entity_plugin->getDefaultDisplay($display, $display->getTargetBundle(), $display->getMode()));
+        $settings['default'] = "{$display->getTargetEntityTypeId()}__{$display->getTargetBundle()}__{$view_mode}__default";
         $display->setThirdPartySetting('panelizer', 'displays', $displays);
       }
 
@@ -564,4 +611,4 @@ class Panelizer implements PanelizerInterface {
     return $this->hasOperationPermission($op, $entity_type_id, $bundle, $account);
   }
 
-}
\ No newline at end of file
+}
diff --git a/src/PanelizerEntityViewBuilder.php b/src/PanelizerEntityViewBuilder.php
index 3a15689..8522322 100644
--- a/src/PanelizerEntityViewBuilder.php
+++ b/src/PanelizerEntityViewBuilder.php
@@ -352,6 +352,8 @@ class PanelizerEntityViewBuilder implements EntityViewBuilderInterface, EntityHa
 
     foreach ($entities as $id => $entity) {
       $panels_display = $this->panelizer->getPanelsDisplay($entity, $view_mode, $displays[$entity->bundle()]);
+      $settings = $this->panelizer->getPanelizerSettings($entity->getEntityTypeId(), $entity->bundle(), $view_mode, $displays[$entity->bundle()]);
+      $panels_display->setContexts($this->panelizer->getDisplayStaticContexts($settings['default'], $entity->getEntityTypeId(), $entity->bundle(), $view_mode, $displays[$entity->bundle()]));
       $build[$id] = $this->buildPanelized($entity, $panels_display, $view_mode, $langcode);
     }
 
diff --git a/src/PanelizerInterface.php b/src/PanelizerInterface.php
index 0372277..1ffe4d9 100644
--- a/src/PanelizerInterface.php
+++ b/src/PanelizerInterface.php
@@ -115,6 +115,12 @@ interface PanelizerInterface {
    */
   public function setDefaultPanelsDisplay($name, $entity_type_id, $bundle, $view_mode, PanelsDisplayVariant $panels_display);
 
+  public function getDisplayStaticContexts($name, $entity_type_id, $bundle, $view_mode, EntityViewDisplayInterface $display = NULL);
+
+  public function setDisplayStaticContexts($name, $entity_type_id, $bundle, $view_mode, $contexts);
+
+
+
   /**
    * Checks if the given entity type, bundle and view mode are panelized.
    *
diff --git a/src/Plugin/AddDefaultLinkDeriver.php b/src/Plugin/AddDefaultLinkDeriver.php
new file mode 100644
index 0000000..5e9d620
--- /dev/null
+++ b/src/Plugin/AddDefaultLinkDeriver.php
@@ -0,0 +1,66 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\panelizer\Plugin\AddDefaultLinkDeriver.
+ */
+
+namespace Drupal\panelizer\Plugin;
+
+use Drupal\Component\Plugin\Derivative\DeriverBase;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+class AddDefaultLinkDeriver extends DeriverBase implements ContainerDeriverInterface {
+
+  /**
+   * @var \Drupal\panelizer\Plugin\PanelizerEntityManagerInterface
+   */
+  protected $panelizerEntityManager;
+
+  /**
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * AddDefaultLinkDeriver constructor.
+   *
+   * @param \Drupal\panelizer\Plugin\PanelizerEntityManagerInterface $panelizer_entity_manager
+   *   The panelizer entity manager.
+   */
+  public function __construct(PanelizerEntityManagerInterface $panelizer_entity_manager, EntityTypeManagerInterface $entity_type_manager) {
+    $this->panelizerEntityManager = $panelizer_entity_manager;
+    $this->entityTypeManager = $entity_type_manager;
+  }
+
+  /**
+   * @param \Symfony\Component\DependencyInjection\ContainerInterface $container
+   *   The service container.
+   * @param string $base_plugin_id
+   *   The plugin id.
+   *
+   * @return static
+   */
+  public static function create(ContainerInterface $container, $base_plugin_id) {
+    return new static($container->get('plugin.manager.panelizer_entity'), $container->get('entity_type.manager'));
+  }
+
+
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDerivativeDefinitions($base_plugin_definition) {
+    foreach ($this->panelizerEntityManager->getDefinitions() as $plugin_id => $definition) {
+      $this->derivatives["$plugin_id"] = $base_plugin_definition;
+      $this->derivatives["$plugin_id"]['appears_on'] = [
+        "entity.entity_view_display.$plugin_id.default",
+        "entity.entity_view_display.$plugin_id.view_mode"
+      ];
+    }
+    return parent::getDerivativeDefinitions($base_plugin_definition);
+  }
+
+}
diff --git a/src/Plugin/PanelizerEntity/PanelizerNode.php b/src/Plugin/PanelizerEntity/PanelizerNode.php
index f5e387c..9b9fa62 100644
--- a/src/Plugin/PanelizerEntity/PanelizerNode.php
+++ b/src/Plugin/PanelizerEntity/PanelizerNode.php
@@ -25,6 +25,7 @@ class PanelizerNode extends PanelizerEntityBase {
   public function getDefaultDisplay(EntityViewDisplayInterface $display, $bundle, $view_mode) {
     $panels_display = parent::getDefaultDisplay($display, $bundle, $view_mode)
       ->setPageTitle('[node:title]');
+    $panels_display->setConfiguration(['label' => $this->t('Default')] + $panels_display->getConfiguration());
 
     // Remove the 'title' block because it's covered already.
     foreach ($panels_display->getRegionAssignments() as $region => $blocks) {
diff --git a/src/Plugin/PanelizerEntityBase.php b/src/Plugin/PanelizerEntityBase.php
index d7ea1d6..be8ae80 100644
--- a/src/Plugin/PanelizerEntityBase.php
+++ b/src/Plugin/PanelizerEntityBase.php
@@ -12,6 +12,7 @@ use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
 use Drupal\Core\Entity\EntityFieldManagerInterface;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
 use Drupal\Panels\PanelsDisplayManager;
 use Drupal\panels\Plugin\DisplayVariant\PanelsDisplayVariant;
 use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -20,6 +21,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
  * Base class for Panelizer entity plugins.
  */
 abstract class PanelizerEntityBase extends PluginBase implements PanelizerEntityInterface, ContainerFactoryPluginInterface {
+  use StringTranslationTrait;
 
   /**
    * @var \Drupal\Panels\PanelsDisplayManager
diff --git a/src/Plugin/PanelsStorage/PanelizerDefaultPanelsStorage.php b/src/Plugin/PanelsStorage/PanelizerDefaultPanelsStorage.php
index 72009c9..f2c3964 100644
--- a/src/Plugin/PanelsStorage/PanelizerDefaultPanelsStorage.php
+++ b/src/Plugin/PanelsStorage/PanelizerDefaultPanelsStorage.php
@@ -122,13 +122,20 @@ class PanelizerDefaultPanelsStorage extends PanelsStorageBase implements Contain
    * @param \Drupal\Core\Entity\EntityInterface|NULL $entity
    *   The entity.
    *
-   * @return \Drupal\Core\Plugin\Context\Context
-   *   The context.
+   * @return \Drupal\Core\Plugin\Context\Context[]
+   *   The available contexts.
    */
   protected function getEntityContext($entity_type_id, EntityInterface $entity = NULL) {
-    return new Context(new ContextDefinition('entity:' . $entity_type_id, NULL, TRUE), $entity);
+    $contexts = [];
+    // Set a placeholder context so that the calling code knows that we need
+    // an entity context. If we have the value available, then we actually set
+    // the context value.
+    $contexts['@panelizer.entity_context:entity'] = new Context(new ContextDefinition('entity:' . $entity_type_id, NULL, TRUE), $entity);
+    return $contexts;
   }
 
+
+
   /**
    * {@inheritdoc}
    */
@@ -136,12 +143,8 @@ class PanelizerDefaultPanelsStorage extends PanelsStorageBase implements Contain
     try {
       list ($entity_type_id, $bundle, $view_mode, $name, $entity) = $this->parseId($id);
       if ($panels_display = $this->panelizer->getDefaultPanelsDisplay($name, $entity_type_id, $bundle, $view_mode)) {
-        // Set a placeholder context so that the calling code knows that we need
-        // an entity context. If we have the value available, then we actually set
-        // the context value.
-        $contexts = [
-          '@panelizer.entity_context:entity' => $this->getEntityContext($entity_type_id, $entity),
-        ];
+        $contexts = $this->getEntityContext($entity_type_id, $entity);
+        $contexts = $contexts + $this->panelizer->getDisplayStaticContexts($name, $entity_type_id, $bundle, $view_mode);
         $panels_display->setContexts($contexts);
         return $panels_display;
       }
diff --git a/src/Tests/PanelizerNodeFunctionalTest.php b/src/Tests/PanelizerNodeFunctionalTest.php
index 668afe6..3ffda7f 100644
--- a/src/Tests/PanelizerNodeFunctionalTest.php
+++ b/src/Tests/PanelizerNodeFunctionalTest.php
@@ -46,24 +46,105 @@ class PanelizerNodeFunctionalTest extends WebTestBase {
     $user = $this->drupalCreateUser([
       'administer node display',
       'administer nodes',
+      'administer pages',
       'administer content types',
       'create page content',
+      'create article content',
       'administer panelizer',
       'access panels in-place editing',
-      'use panels in place editing',
+      'view the administration theme',
     ]);
     $this->drupalLogin($user);
+  }
 
-    $this->drupalPostForm('admin/structure/types/manage/page/display', [
-      'panelizer[enable]' => TRUE,
-      'panelizer[custom]' => TRUE,
-    ], 'Save');
+  /**
+   * Tests the admin interface to set a default layout for a bundle.
+   */
+  public function testWizardUI() {
+    $this->drupalGet('admin/structure/types/manage/article/display');
+    $this->clickLink('Panelize this view mode');
+    // General settings step.
+    $edit = [
+      'enable' => TRUE,
+      'custom' => TRUE,
+    ];
+
+    // Contexts step.
+    $this->drupalPostForm(NULL, $edit, 'Next');
+    $this->assertText('panelizer_context_entity', 'The current entity context is present.');
+
+    // Layout selection step.
+    $this->drupalPostForm(NULL, [], 'Next');
+
+    // Content step. Add the Node block to the top region.
+    $this->drupalPostForm(NULL, [], 'Next');
+    $this->clickLink('Add new block');
+    $this->clickLink('Title');
+    $edit = [
+      'region' => 'middle',
+    ];
+    $this->drupalPostForm(NULL, $edit, 'Add block');
+
+    // Finish the wizard.
+    $this->drupalPostForm(NULL, [], 'Finish');
+
+    // Check that the general setting was saved.
+    $this->assertFieldChecked('edit-custom');
+
+    // Now change the setting and then cancel changes.
+    $this->drupalPostForm(NULL, ['custom' => FALSE], 'Update');
+    $this->assertNoFieldChecked('edit-custom');
+    $this->drupalPostForm(NULL, [], 'Cancel');
+    $this->assertFieldChecked('edit-custom');
+
+    // Now change and save the general setting.
+    $this->drupalPostForm(NULL, ['custom' => FALSE], 'Update and save');
+    $this->assertNoFieldChecked('edit-custom');
+    $this->drupalPostForm(NULL, [], 'Cancel');
+    $this->assertNoFieldChecked('edit-custom');
+
+    // Add another block at the Content step and then save changes.
+    $this->clickLink('Content');
+    $this->clickLink('Add new block');
+    $this->clickLink('Body');
+    $edit = [
+      'region' => 'middle',
+    ];
+    $this->drupalPostForm(NULL, $edit, 'Add block');
+    $this->assertText('entity_field:node:body', 'The body block was added successfully.');
+    $this->drupalPostForm(NULL, [], 'Save');
+    $this->clickLink('Content');
+    $this->assertText('entity_field:node:body', 'The body block was saved successfully.');
+
+    // Check that the Manage Display tab changed now that Panelizer is set up.
+    // Also, the field display table should be hidden.
+    $this->drupalGet('admin/structure/types/manage/article/display');
+    $this->assertLink('This display mode is managed by Panelizer. Click here to go to its settings.');
+    $this->assertNoRaw('<div id="field-display-overview-wrapper">');
+
+    // Disable Panelizer for the default display mode.
+    // This should bring back the field overview table at Manage Display
+    // and make the "Panelize this view mode" link to point to the Edit
+    // Wizard UI.
+    $this->clickLink('This display mode is managed by Panelizer. Click here to go to its settings.');
+    $edit = [
+      'enable' => FALSE,
+    ];
+    $this->drupalPostForm(NULL, $edit, 'Save');
+    $this->drupalGet('admin/structure/types/manage/article/display');
+    $this->assertLink('Panelize this view mode');
+    $this->assertLinkByHref('admin/structure/panelizer/edit/node__article__default');
+    $this->assertRaw('<div id="field-display-overview-wrapper">');
   }
 
   /**
    * Tests rendering a node with Panelizer default.
    */
   public function testPanelizerDefault() {
+    $this->drupalPostForm('admin/structure/types/manage/page/display', [
+      'panelizer[enable]' => TRUE,
+      'panelizer[custom]' => TRUE,
+    ], 'Save');
     /** @var \Drupal\panelizer\PanelizerInterface $panelizer */
     $panelizer = \Drupal::service('panelizer');
     $displays = $panelizer->getDefaultPanelsDisplays('node', 'page', 'default');
diff --git a/src/Wizard/PanelizerAddWizard.php b/src/Wizard/PanelizerAddWizard.php
new file mode 100644
index 0000000..658b3bd
--- /dev/null
+++ b/src/Wizard/PanelizerAddWizard.php
@@ -0,0 +1,35 @@
+<?php
+
+namespace Drupal\panelizer\Wizard;
+
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\panelizer\Form\PanelizerWizardContentForm;
+use Drupal\panelizer\Form\PanelizerWizardContextForm;
+use Drupal\panelizer\Form\PanelizerWizardGeneralForm;
+
+class PanelizerAddWizard extends PanelizerWizardBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getRouteName() {
+    return 'panelizer.wizard.add.step';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state, $entity_type_id = NULL, $bundle = NULL, $view_mode_name = NULL) {
+    if ($entity_type_id && $bundle && $view_mode_name) {
+      $form_state->set('machine_name_prefix', "{$entity_type_id}__{$bundle}__{$view_mode_name}");
+    }
+    $form = parent::buildForm($form, $form_state);
+    $cached_values = $form_state->getTemporaryValue('wizard');
+    $cached_values['id'] = $this->getMachineName();
+    // Some variants like PanelsDisplayVariant need this. Set it to empty.
+    $cached_values['access'] = [];
+    $form_state->setTemporaryValue('wizard', $cached_values);
+    return $form;
+  }
+
+}
diff --git a/src/Wizard/PanelizerEditWizard.php b/src/Wizard/PanelizerEditWizard.php
new file mode 100644
index 0000000..52dd594
--- /dev/null
+++ b/src/Wizard/PanelizerEditWizard.php
@@ -0,0 +1,210 @@
+<?php
+
+namespace Drupal\panelizer\Wizard;
+
+use Drupal\Core\Form\FormBuilderInterface;
+use Drupal\Core\Form\FormInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Url;
+
+class PanelizerEditWizard extends PanelizerWizardBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getRouteName() {
+    return 'panelizer.wizard.edit';
+  }
+
+  public function initValues() {
+    $cached_values = parent::initValues();
+    // Load data in to values to be cached and managed by the
+    // wizard until the user clicks on Save or Cancel.
+    $cached_values['id'] = $this->getMachineName();
+    $cached_values['access'] = [];
+    list($entity_type, $bundle, $view_mode, $display_id) = explode('__', $this->getMachineName());
+    $panelizer = \Drupal::service('panelizer');
+    // Load the panels display variant.
+    /** @var \Drupal\panelizer\Panelizer $panelizer */
+    // @todo this $display_id looks all wrong to me since it's the name and view_mode.
+    $variant_plugin = $panelizer->getDefaultPanelsDisplay($display_id, $entity_type, $bundle, $view_mode);
+    $cached_values['plugin'] = $variant_plugin;
+    $cached_values['label'] = $cached_values['plugin']->getConfiguration()['label'];
+
+    $display = $panelizer->getEntityViewDisplay($entity_type, $bundle, $view_mode);
+    $config = $display->getThirdPartySetting('panelizer', 'displays', []);
+    if (!empty($config[$display_id]['static_context'])) {
+      $cached_values['contexts'] = $config[$display_id]['static_context'];
+    }
+    return $cached_values;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function customizeForm(array $form, FormStateInterface $form_state) {
+    // The page actions.
+    $form['wizard_actions'] = [
+      '#theme' => 'links',
+      '#links' => [],
+      '#attributes' => [
+        'class' => ['inline'],
+      ]
+    ];
+
+    // The tree of wizard steps.
+    $form['wizard_tree'] = [
+      '#theme' => ['panelizer_wizard_tree'],
+      '#wizard' => $this,
+      '#cached_values' => $form_state->getTemporaryValue('wizard'),
+    ];
+
+    $form['#theme'] = 'panelizer_wizard_form';
+    $form['#attached']['library'][] = 'panelizer/wizard_admin';
+    $form = parent::customizeForm($form, $form_state);
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function actions(FormInterface $form_object, FormStateInterface $form_state) {
+    $cached_values = $form_state->getTemporaryValue('wizard');
+    $operation = $this->getOperation($cached_values);
+
+    $actions = [];
+
+    $actions['submit'] = [
+      '#type' => 'submit',
+      '#value' => $this->t('Update'),
+      '#validate' => [
+        '::populateCachedValues',
+        [$form_object, 'validateForm'],
+      ],
+      '#submit' => [
+        [$form_object, 'submitForm'],
+      ],
+    ];
+
+    $actions['update_and_save'] = [
+      '#type' => 'submit',
+      '#value' => $this->t('Update and save'),
+      '#button_type' => 'primary',
+      '#validate' => [
+        '::populateCachedValues',
+        [$form_object, 'validateForm'],
+      ],
+      '#submit' => [
+        [$form_object, 'submitForm'],
+      ],
+    ];
+
+    $actions['finish'] = [
+      '#type' => 'submit',
+      '#value' => $this->t('Save'),
+      '#validate' => [
+        '::populateCachedValues',
+        [$form_object, 'validateForm'],
+      ],
+      '#submit' => [
+        [$form_object, 'submitForm'],
+      ],
+    ];
+
+    $actions['cancel'] = [
+      '#type' => 'submit',
+      '#value' => $this->t('Cancel'),
+      '#submit' => [
+        '::clearTempstore'
+      ],
+    ];
+
+    // Add any submit or validate functions for the step and the global ones.
+    foreach (['submit', 'update_and_save', 'finish'] as $button) {
+      if (isset($operation['validate'])) {
+        $actions[$button]['#validate'] = array_merge($actions[$button]['#validate'], $operation['validate']);
+      }
+      $actions[$button]['#validate'][] = '::validateForm';
+      if (isset($operation['submit'])) {
+        $actions[$button]['#submit'] = array_merge($actions[$button]['#submit'], $operation['submit']);
+      }
+      $actions[$button]['#submit'][] = '::submitForm';
+    }
+    $actions['update_and_save']['#submit'][] = '::finish';
+    $actions['finish']['#submit'][] = '::finish';
+
+    if ($form_state->get('ajax')) {
+      $cached_values = $form_state->getTemporaryValue('wizard');
+      $ajax_parameters = $this->getNextParameters($cached_values);
+      $ajax_parameters['step'] = $this->getStep($cached_values);
+      $actions['submit']['#ajax'] = [
+        'callback' => '::ajaxSubmit',
+        'url' => Url::fromRoute($this->getRouteName(), $ajax_parameters),
+        'options' => ['query' => \Drupal::request()->query->all() + [FormBuilderInterface::AJAX_FORM_REQUEST => TRUE]],
+      ];
+      $actions['update_and_save']['#ajax'] = [
+        'callback' => '::ajaxFinish',
+        'url' => Url::fromRoute($this->getRouteName(), $ajax_parameters),
+        'options' => ['query' => \Drupal::request()->query->all() + [FormBuilderInterface::AJAX_FORM_REQUEST => TRUE]],
+      ];
+      $actions['finish']['#ajax'] = [
+        'callback' => '::ajaxFinish',
+        'url' => Url::fromRoute($this->getRouteName(), $ajax_parameters),
+        'options' => ['query' => \Drupal::request()->query->all() + [FormBuilderInterface::AJAX_FORM_REQUEST => TRUE]],
+      ];
+    }
+
+    return $actions;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    if (in_array((string)$form_state->getValue('op'), [(string)$this->getNextOp(), (string)$this->t('Update'), (string)$this->t('Update and save'), (string)$this->t('Save')])) {
+      $cached_values = $form_state->getTemporaryValue('wizard');
+      if ($form_state->hasValue('label')) {
+        $config = $cached_values['plugin']->getConfiguration();
+        $config['label'] = $form_state->getValue('label');
+        $cached_values['plugin']->setConfiguration($config);
+      }
+      if ($form_state->hasValue('id')) {
+        $cached_values['id'] = $form_state->getValue('id');
+      }
+      if (is_null($this->machine_name) && !empty($cached_values['id'])) {
+        $this->machine_name = $cached_values['id'];
+      }
+      $this->getTempstore()->set($this->getMachineName(), $cached_values);
+      if (!$form_state->get('ajax')) {
+        $form_state->setRedirect($this->getRouteName(), $this->getNextParameters($cached_values));
+      }
+    }
+  }
+
+  /**
+   * Clears the temporary store.
+   *
+   * @param array $form
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   */
+  public function clearTempstore(array &$form, FormStateInterface $form_state) {
+    $this->getTempstore()->delete($this->getMachineName());
+    list($entity_type_id, $bundle, $view_mode) = explode('__', $this->getMachineName());
+    $bundle_entity_type = \Drupal::entityTypeManager()->getDefinition($entity_type_id)->getBundleEntityType();
+    if ($view_mode == 'default') {
+      $route = "entity.entity_view_display.{$entity_type_id}.default";
+      $arguments = [
+        $bundle_entity_type => $bundle,
+      ];
+    }
+    else {
+      $route = "entity.entity_view_display.{$entity_type_id}.view_mode";
+      $arguments = [
+        $bundle_entity_type => $bundle,
+        'view_mode_name' => $view_mode,
+      ];
+    }
+    $form_state->setRedirect($route, $arguments);
+  }
+
+}
diff --git a/src/Wizard/PanelizerWizardBase.php b/src/Wizard/PanelizerWizardBase.php
new file mode 100644
index 0000000..ec4be7a
--- /dev/null
+++ b/src/Wizard/PanelizerWizardBase.php
@@ -0,0 +1,148 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\panelizer\Wizard\PanelizerWizardBase.
+ */
+
+namespace Drupal\panelizer\Wizard;
+
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\ctools\Wizard\FormWizardBase;
+use Drupal\panelizer\Form\PanelizerWizardContentForm;
+use Drupal\panelizer\Form\PanelizerWizardContextForm;
+use Drupal\panelizer\Form\PanelizerWizardGeneralForm;
+
+abstract class PanelizerWizardBase extends FormWizardBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function customizeForm(array $form, FormStateInterface $form_state) {
+    $form = parent::customizeForm($form, $form_state);
+    $cached_values = $form_state->getTemporaryValue('wizard');
+    // Get the current form operation.
+    $operation = $this->getOperation($cached_values);
+    $operations = $this->getOperations($cached_values);
+    $default_operation = reset($operations);
+    if ($operation['form'] == $default_operation['form']) {
+      // Create id and label form elements.
+      $form['name'] = array(
+        '#type' => 'fieldset',
+        '#attributes' => array('class' => array('fieldset-no-legend')),
+        '#title' => $this->getWizardLabel(),
+      );
+      $form['name']['label'] = array(
+        '#type' => 'textfield',
+        '#title' => $this->getMachineLabel(),
+        '#required' => TRUE,
+        '#size' => 32,
+        '#default_value' => !empty($cached_values['label']) ? $cached_values['label'] : '',
+        '#maxlength' => 255,
+        '#disabled' => !empty($cached_values['label']),
+      );
+      $form['name']['id'] = array(
+        '#type' => 'machine_name',
+        '#maxlength' => 128,
+        '#machine_name' => array(
+          'source' => array('name', 'label'),
+        ),
+        '#description' => $this->t('A unique machine-readable name for this display. It must only contain lowercase letters, numbers, and underscores.'),
+        '#default_value' => !empty($cached_values['id']) ? $cached_values['id'] : '',
+        '#disabled' => !empty($cached_values['id']),
+      );
+    }
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getWizardLabel() {
+    return $this->t('Wizard Information');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getMachineLabel() {
+    return $this->t('Wizard name');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getOperations($cached_values) {
+    $operations = [
+      'general' => [
+        'form' => PanelizerWizardGeneralForm::class,
+        'title' => $this->t('General settings'),
+      ],
+      'contexts' => [
+        'form' => PanelizerWizardContextForm::class,
+        'title' => $this->t('Contexts'),
+      ],
+    ];
+
+    // Add any wizard operations from the plugin itself.
+    foreach ($cached_values['plugin']->getWizardOperations($cached_values) as $name => $operation) {
+      $operations[$name] = $operation;
+    }
+
+    // Change the class that manages the Content step.
+    if (isset($operations['content'])) {
+      $operations['content']['form'] = PanelizerWizardContentForm::class;
+    }
+
+    return $operations;
+  }
+
+  public function initValues() {
+    $cached_values = parent::initValues();
+    if (empty($cached_values['plugin'])) {
+      $plugin = \Drupal::service('plugin.manager.display_variant')->createInstance('panels_variant');
+      $cached_values['plugin'] = $plugin;
+    }
+    if (empty($cached_values['contexts'])) {
+      $cached_values['contexts'] = [];
+    }
+    return $cached_values;
+  }
+
+
+  /**
+   * {@inheritdoc}
+   */
+  public function finish(array &$form, FormStateInterface $form_state) {
+    $cached_values = $form_state->getTemporaryValue('wizard');
+
+    // Save the panels display mode and its custom settings as third party
+    // data of the display mode for this entity+bundle+display.
+    /** @var \Drupal\panelizer\Panelizer $panelizer */
+    $panelizer = \Drupal::service('panelizer');
+    list($entity_type, $bundle, $view_mode, $display_id) = explode('__', $cached_values['id']);
+    $entity_view_display = $panelizer->getEntityViewDisplay($entity_type, $bundle, $view_mode);
+    $panelizer->setDefaultPanelsDisplay($display_id, $entity_type, $bundle, $view_mode, $cached_values['plugin']);
+    $panelizer->setDisplayStaticContexts($display_id, $entity_type, $bundle, $view_mode, $cached_values['contexts']);
+
+    parent::finish($form, $form_state);
+    $form_state->setRedirect('panelizer.wizard.edit', ['machine_name' => $cached_values['id']]);
+  }
+
+  /**
+   * Wraps the context mapper.
+   *
+   * @return \Drupal\ctools\ContextMapperInterface
+   */
+  protected function getContextMapper() {
+    return \Drupal::service('ctools.context_mapper');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getContexts($cached_values) {
+    return $this->getContextMapper()->getContextValues($cached_values['contexts']);
+  }
+
+}
diff --git a/templates/panelizer-wizard-form.html.twig b/templates/panelizer-wizard-form.html.twig
new file mode 100644
index 0000000..a691162
--- /dev/null
+++ b/templates/panelizer-wizard-form.html.twig
@@ -0,0 +1,31 @@
+{#
+/**
+ * @file
+ * Default theme implementation for a 'form' element.
+ *
+ * Available variables
+ * - attributes: A list of HTML attributes for the wrapper element.
+ * - children: The child elements of the form.
+ *
+ * @see template_preprocess_form()
+ *
+ * @ingroup themeable
+ */
+#}
+<div class="panelizer-wizard">
+  <div class="panelizer-wizard-actions">
+    {{ form.wizard_actions }}
+  </div>
+  <div class="panelizer-wizard-main clearfix">
+    <div class="panelizer-wizard-tree">
+      {{ form.wizard_tree }}
+    </div>
+    <div class="panelizer-wizard-form">
+      {{ form|without('wizard_actions', 'wizard_tree', 'actions') }}
+    </div>
+  </div>
+
+  <div class="panelizer-wizard-form-actions">
+    {{ form.actions }}
+  </div>
+</div>
diff --git a/templates/panelizer-wizard-tree.html.twig b/templates/panelizer-wizard-tree.html.twig
new file mode 100644
index 0000000..7d48fcf
--- /dev/null
+++ b/templates/panelizer-wizard-tree.html.twig
@@ -0,0 +1,47 @@
+{#
+/**
+ * @file
+ * Default theme implementation to display wizard tree.
+ *
+ * Available variables:
+ * - step: The current step name.
+ * - tree: A nested list of menu items. Each menu item contains:
+ *   - title: The menu link title.
+ *   - url: The menu link url, instance of \Drupal\Core\Url
+ *   - children: The menu item child items.
+ *   - step: The name of the step.
+ *
+ * @ingroup themeable
+ */
+#}
+{% import _self as panelizer %}
+
+{#
+  We call a macro which calls itself to render the full tree.
+  @see http://twig.sensiolabs.org/doc/tags/macro.html
+#}
+{{ panelizer.wizard_tree(tree, step, 0) }}
+
+{% macro wizard_tree(items, step, menu_level) %}
+  {% import _self as panelizer %}
+  {% if items %}
+    <ul>
+    {% for item in items %}
+      <li>
+        {% if item.url %}
+          {% if step is same as(item.step) %}
+            <strong>{{ link(item.title, item.url) }}</strong>
+          {% else %}
+            {{ link(item.title, item.url) }}
+          {% endif %}
+        {% else %}
+          {{ item.title }}
+        {% endif %}
+        {% if item.children %}
+          {{ panelizer.wizard_tree(item.children, step, menu_level + 1) }}
+        {% endif %}
+      </li>
+    {% endfor %}
+    </ul>
+  {% endif %}
+{% endmacro %}
