diff --git a/config/schema/page_manager.schema.yml b/config/schema/page_manager.schema.yml
index ad1f22c..2cbf4f7 100644
--- a/config/schema/page_manager.schema.yml
+++ b/config/schema/page_manager.schema.yml
@@ -8,6 +8,9 @@ page_manager.page.*:
     label:
       type: label
       label: 'Label'
+    description:
+      type: text
+      label: 'Description'
     use_admin_theme:
       type: boolean
       label: 'Whether the page is displayed using the admin theme or not'
diff --git a/css/page-manager.admin.css b/css/page-manager.admin.css
new file mode 100644
index 0000000..39afd04
--- /dev/null
+++ b/css/page-manager.admin.css
@@ -0,0 +1,114 @@
+/**
+ * @file
+ * Styles for Page Manager admin.
+ */
+
+/* Narrow screens */
+
+.page-manager-wizard-tree,
+.page-manager-wizard-form {
+  box-sizing: border-box;
+}
+
+/**
+ * Wizard actions across the top.
+ */
+.page-manager-wizard-actions {
+  text-align: right; /* LTR */
+}
+.page-manager-wizard-actions ul.inline,
+.page-manager-wizard-actions ul.inline li {
+  display: inline-block;
+  margin: 0;
+}
+.page-manager-wizard-actions ul.inline {
+  border-top: 1px solid black;
+  border-left: 1px solid black;
+}
+.page-manager-wizard-actions ul.inline li {
+  border-right: 1px solid black;
+  padding: .5em;
+}
+
+/**
+ * The tree of wizard steps.
+ */
+.page-manager-wizard-tree ul {
+  margin: 0;
+  padding: 0;
+  list-style: none;
+}
+.page-manager-wizard-tree ul > li > ul {
+  margin-left: 1em;
+}
+.page-manager-wizard-tree > ul {
+  border: 1px solid black;
+  padding-bottom: .5em;
+  margin-bottom: 20px;
+}
+.page-manager-wizard-tree li {
+  border-bottom: 1px solid black;
+  padding: .5em;
+  padding-right: 0;
+}
+.page-manager-wizard-tree li:last-child {
+  border-bottom: 0;
+  padding-bottom: 0;
+}
+
+/**
+ * The wizard form.
+ */
+.page-manager-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.
+   */
+  .page-manager-wizard-tree {
+    float: left; /* LTR */
+    width: 20%;
+  }
+  .page-manager-wizard-form {
+    float: left; /* LTR */
+    width: 80%;
+  }
+  .page-manager-wizard-form-actions {
+    margin-left: 20%; /* LTR */
+  }
+
+  /**
+   * Make the borders look nice.
+   */
+  .page-manager-wizard-tree > ul {
+    border-right: 0; /* LTR */
+  }
+  .page-manager-wizard-form {
+    min-height: 700px;
+  }
+
+  /**
+   * Right-to-left support.
+   */
+  [dir="rtl"] .page-manager-wizard-tree,
+  [dir="rtl"] .page-manager-wizard-form {
+    float: right;
+  }
+  [dir="rtl"] .page-manager-wizard-form-actions {
+    margin-left: 0;
+    margin-right: 20%;
+  }
+  [dir="rtl"] .page-manager-wizard-tree > ul {
+    border-right: 1px solid black;
+    border-left: 0;
+  }
+}
+
diff --git a/page_manager.libraries.yml b/page_manager.libraries.yml
new file mode 100644
index 0000000..14f4d82
--- /dev/null
+++ b/page_manager.libraries.yml
@@ -0,0 +1,5 @@
+admin:
+  version: VERSION
+  css:
+    layout:
+      css/page-manager.admin.css: {}
diff --git a/page_manager.module b/page_manager.module
new file mode 100644
index 0000000..aa70511
--- /dev/null
+++ b/page_manager.module
@@ -0,0 +1,65 @@
+<?php
+/**
+ * @file
+ * Contains hooks for page_manager module.
+ */
+
+/**
+ * Implements hook_theme().
+ */
+function page_manager_theme() {
+  return [
+    'page_manager_wizard_form' => [
+      'render element' => 'form',
+    ],
+    'page_manager_wizard_tree' => [
+      'variables' => [
+        'wizard' => NULL,
+        'cached_values' => [],
+        'tree' => [],
+        'divider' => ' » ',
+        'step' => NULL,
+      ],
+    ],
+  ];
+}
+
+/**
+ * Preprocess function for page-manager-wizard-tree.html.twig.
+ */
+function template_preprocess_page_manager_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;
+}
diff --git a/page_manager.routing.yml b/page_manager.routing.yml
index 410e1c3..3ab423a 100644
--- a/page_manager.routing.yml
+++ b/page_manager.routing.yml
@@ -11,18 +11,29 @@ entity.page.collection:
 entity.page.add_form:
   path: '/admin/structure/page_manager/add'
   defaults:
-    _entity_form: 'page.add'
+    _entity_wizard: 'page.add'
     _title: 'Add new page'
+    tempstore_id: page_manager.page
+  requirements:
+    _entity_create_access: page
+
+entity.page.add_step_form:
+  path: '/admin/structure/page_manager/add/{machine_name}/{step}'
+  defaults:
+    _entity_wizard: 'page.add'
+    _title: 'Add new page'
+    tempstore_id: page_manager.page
   requirements:
     _entity_create_access: page
 
 entity.page.edit_form:
-  path: '/admin/structure/page_manager/manage/{page}'
+  path: '/admin/structure/page_manager/manage/{machine_name}/{step}'
   defaults:
-    _entity_form: 'page.edit'
+    _entity_wizard: 'page.edit'
     _title_callback: '\Drupal\page_manager\Controller\PageManagerController::editPageTitle'
+    tempstore_id: page_manager.page
   requirements:
-    _entity_access: page.update
+    _permission: 'administer pages'
 
 entity.page.delete_form:
   path: '/admin/structure/page_manager/manage/{page}/delete'
@@ -30,7 +41,7 @@ entity.page.delete_form:
     _entity_form: 'page.delete'
     _title: 'Delete page'
   requirements:
-    _entity_access: page.delete
+    _permission: 'administer pages'
 
 entity.page.enable:
   path: '/admin/structure/page_manager/manage/{page}/enable'
@@ -38,7 +49,7 @@ entity.page.enable:
     _controller: '\Drupal\page_manager\Controller\PageManagerController::performPageOperation'
     op: 'enable'
   requirements:
-    _entity_access: 'page.update'
+    _permission: 'administer pages'
 
 entity.page.disable:
   path: '/admin/structure/page_manager/manage/{page}/disable'
@@ -46,164 +57,159 @@ entity.page.disable:
     _controller: '\Drupal\page_manager\Controller\PageManagerController::performPageOperation'
     op: 'disable'
   requirements:
-    _entity_access: 'page.update'
-
-#### Access Conditions
+    _permission: 'administer pages'
 
-page_manager.access_condition_select:
-  path: '/admin/structure/page_manager/manage/{page}/access/select'
+entity.page.reorder_variants_form:
+  path: '/admin/structure/page_manager/manage/{machine_name}/reorder_variants'
   defaults:
-    _controller: '\Drupal\page_manager\Controller\PageManagerController::selectAccessCondition'
-    _title: 'Select access condition'
+    _title: 'Reorder variants'
+    _form: '\Drupal\page_manager\Form\PageReorderVariantsForm'
   requirements:
-    _entity_access: page.update
+    _permission: 'administer pages'
+
+#### Access Conditions
 
-page_manager.access_condition_add:
-  path: '/admin/structure/page_manager/manage/{page}/access/add/{condition_id}'
+entity.page.condition.add:
+  path: '/admin/structure/page_manager/manage/{machine_name}/access/add/{condition}'
   defaults:
-    _form: '\Drupal\page_manager\Form\AccessConditionAddForm'
-    _title: 'Add new access condition'
+    _form: 'Drupal\page_manager\Form\AccessConfigure'
+    _title: 'Add access condition'
+    tempstore_id: page_manager.page
   requirements:
-    _entity_access: page.update
+    _permission: 'administer pages'
 
-page_manager.access_condition_edit:
-  path: '/admin/structure/page_manager/manage/{page}/access/edit/{condition_id}'
+entity.page.condition.edit:
+  path: '/admin/structure/page_manager/manage/{machine_name}/access/edit/{condition}'
   defaults:
-    _form: '\Drupal\page_manager\Form\AccessConditionEditForm'
-    _title_callback: '\Drupal\page_manager\Controller\PageManagerController::editAccessConditionTitle'
+    _form: 'Drupal\page_manager\Form\AccessConfigure'
+    _title: 'Edit access condition'
+    tempstore_id: page_manager.page
   requirements:
-    _entity_access: page.update
+    _permission: 'administer pages'
 
-page_manager.access_condition_delete:
-  path: '/admin/structure/page_manager/manage/{page}/access/delete/{condition_id}'
+entity.page.condition.delete:
+  path: '/admin/structure/page_manager/manage/{machine_name}/access/delete/{id}'
   defaults:
-    _form: '\Drupal\page_manager\Form\AccessConditionDeleteForm'
+    _form: 'Drupal\page_manager\Form\AccessDelete'
     _title: 'Delete access condition'
+    tempstore_id: page_manager.page
   requirements:
-    _entity_access: page.update
+    _permission: 'administer pages'
 
 #### Static Contexts
 
 page_manager.static_context_add:
-  path: '/admin/structure/page_manager/manage/{page}/context/add'
+  path: '/admin/structure/page_manager/manage/{machine_name}/context/add'
   defaults:
     _form: '\Drupal\page_manager\Form\StaticContextAddForm'
     _title: 'Add new static context'
   requirements:
-    _entity_access: page.update
+    _permission: 'administer pages'
 
 page_manager.static_context_edit:
-  path: '/admin/structure/page_manager/manage/{page}/context/edit/{name}'
+  path: '/admin/structure/page_manager/manage/{machine_name}/context/edit/{name}'
   defaults:
     _form: '\Drupal\page_manager\Form\StaticContextEditForm'
     _title_callback: '\Drupal\page_manager\Controller\PageManagerController::editStaticContextTitle'
   requirements:
-    _entity_access: page.update
+    _permission: 'administer pages'
 
 page_manager.static_context_delete:
-  path: '/admin/structure/page_manager/manage/{page}/context/delete/{name}'
+  path: '/admin/structure/page_manager/manage/{machine_name}/context/delete/{name}'
   defaults:
     _form: '\Drupal\page_manager\Form\StaticContextDeleteForm'
     _title: 'Delete static context'
   requirements:
-    _entity_access: page.update
+    _permission: 'administer pages'
 
 #### Variants
 
 page_manager.variant_select:
-  path: '/admin/structure/page_manager/manage/{page}/add'
+  path: '/admin/structure/page_manager/manage/{machine_name}/add'
   defaults:
     _controller: '\Drupal\page_manager\Controller\PageManagerController::selectVariant'
     _title: 'Select variant'
   requirements:
-    _entity_access: page.update
+    _permission: 'administer pages'
 
 entity.page_variant.add_form:
-  path: '/admin/structure/page_manager/manage/{page}/add/{variant_plugin_id}'
+  path: '/admin/structure/page_manager/manage/{machine_name}/add_variant'
   defaults:
-    _controller: '\Drupal\page_manager\Controller\PageManagerController::addPageVariantEntityForm'
     _title: 'Add page variant'
+    _form: '\Drupal\page_manager\Form\PageVariantAddForm'
   requirements:
-    _entity_create_access: page_variant
-
-entity.page_variant.edit_form:
-  path: '/admin/structure/page_manager/manage/{page}/variant/{page_variant}'
-  defaults:
-    _entity_form: 'page_variant.edit'
-    _title_callback: '\Drupal\page_manager\Controller\PageManagerController::editPageVariantTitle'
-  requirements:
-    _entity_access: page_variant.update
+    _permission: 'administer pages'
 
 entity.page_variant.delete_form:
-  path: '/admin/structure/page_variant/variant/{page_variant}/delete'
+  path: '/admin/structure/page_manager/manage/{machine_name}/variant/{variant_machine_name}/delete'
   defaults:
-    _entity_form: 'page_variant.delete'
+    _form: '\Drupal\page_manager\Form\PageVariantDeleteForm'
     _title: 'Delete page variant'
+    tempstore_id: page_manager.page
   requirements:
-    _entity_access: page_variant.delete
+    _permission: 'administer pages'
 
-page_manager.variant_select_block:
-  path: '/admin/structure/page_manager/manage/{page}/variant/{page_variant}/block/select'
+page_manager.block_display_select_block:
+  path: '/admin/structure/page_manager/block_display/{block_display}/select'
   defaults:
     _controller: '\Drupal\page_manager\Controller\PageManagerController::selectBlock'
     _title: 'Select block'
   requirements:
-    _entity_access: page_variant.update
+    # @todo: what permissions can we use for the block display?
+    _permission: 'administer pages'
 
-page_manager.variant_add_block:
-  path: '/admin/structure/page_manager/manage/{page}/variant/{page_variant}/block/add/{block_id}'
+page_manager.block_display_add_block:
+  path: '/admin/structure/page_manager/block_display/{block_display}/add/{block_id}'
   defaults:
     _form: '\Drupal\page_manager\Form\VariantPluginAddBlockForm'
-    _title: 'Add block to variant'
+    _title: 'Add block'
+    tempstore_id: 'page_manager.block_display'
   requirements:
-    _entity_access: page_variant.update
+    _ctools_access: 'block_display'
 
-page_manager.variant_edit_block:
-  path: '/admin/structure/page_manager/manage/{page}/variant/{page_variant}/block/edit/{block_id}'
+page_manager.block_display_edit_block:
+  path: '/admin/structure/page_manager/block_display/{block_display}/edit/{block_id}'
   defaults:
     _form: '\Drupal\page_manager\Form\VariantPluginEditBlockForm'
-    _title: 'Edit block in variant'
+    _title: 'Edit block'
   requirements:
-    _entity_access: page_variant.update
+    # @todo: what permissions can we use for the block display?
+    _permission: 'administer pages'
 
-page_manager.variant_delete_block:
-  path: '/admin/structure/page_manager/manage/{page}/variant/{page_variant}/block/delete/{block_id}'
+page_manager.block_display_delete_block:
+  path: '/admin/structure/page_manager/block_display/{block_display}/delete/{block_id}'
   defaults:
     _form: '\Drupal\page_manager\Form\VariantPluginDeleteBlockForm'
-    _title: 'Delete block in variant'
+    _title: 'Delete block'
   requirements:
-    _entity_access: page_variant.update
+    # @todo: what permissions can we use for the block display?
+    _permission: 'administer pages'
 
 #### Selection Conditions
 
-page_manager.selection_condition_select:
-  path: '/admin/structure/page_manager/manage/{page}/variant/{page_variant}/selection/select'
-  defaults:
-    _controller: '\Drupal\page_manager\Controller\PageManagerController::selectSelectionCondition'
-    _title: 'Select selection condition'
-  requirements:
-    _entity_access: page_variant.update
-
-page_manager.selection_condition_add:
-  path: '/admin/structure/page_manager/manage/{page}/variant/{page_variant}/selection/add/{condition_id}'
+entity.page_variant.condition.add:
+  path: '/admin/structure/page_manager/manage/{machine_name}/variant/{variant_machine_name}/selection/add/{condition}'
   defaults:
-    _form: '\Drupal\page_manager\Form\SelectionConditionAddForm'
+    _form: 'Drupal\page_manager\Form\SelectionConfigure'
+    tempstore_id: page_manager.page
     _title: 'Add new selection condition'
   requirements:
-    _entity_access: page_variant.update
+    _permission: 'administer pages'
 
-page_manager.selection_condition_edit:
-  path: '/admin/structure/page_manager/manage/{page}/variant/{page_variant}/selection/edit/{condition_id}'
+entity.page_variant.condition.edit:
+  path: '/admin/structure/page_manager/manage/{machine_name}/variant/{variant_machine_name}/selection/edit/{condition}'
   defaults:
-    _form: '\Drupal\page_manager\Form\SelectionConditionEditForm'
-    _title_callback: '\Drupal\page_manager\Controller\PageManagerController::editSelectionConditionTitle'
+    _form: 'Drupal\page_manager\Form\SelectionConfigure'
+    tempstore_id: page_manager.page
+    _title: 'Edit selection condition'
   requirements:
-    _entity_access: page_variant.update
+    _permission: 'administer pages'
 
-page_manager.selection_condition_delete:
-  path: '/admin/structure/page_manager/manage/{page}/variant/{page_variant}/selection/delete/{condition_id}'
+entity.page_variant.condition.delete:
+  path: '/admin/structure/page_manager/manage/{machine_name}/variant/{variant_machine_name}/selection/delete/{id}'
   defaults:
-    _form: '\Drupal\page_manager\Form\SelectionConditionDeleteForm'
+    _form: 'Drupal\page_manager\Form\SelectionDelete'
+    tempstore_id: page_manager.page
     _title: 'Delete selection condition'
   requirements:
-    _entity_access: page_variant.update
+    _permission: 'administer pages'
diff --git a/src/Access/PageManagerPluginAccess.php b/src/Access/PageManagerPluginAccess.php
new file mode 100644
index 0000000..347d636
--- /dev/null
+++ b/src/Access/PageManagerPluginAccess.php
@@ -0,0 +1,19 @@
+<?php
+/**
+ * @file
+ * Contains \Drupal\page_manager\Access\PageManagerPluginAccess.
+ */
+
+namespace Drupal\page_manager\Access;
+
+use Drupal\Core\Access\AccessResult;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\ctools\Access\AccessInterface;
+
+class PageManagerPluginAccess implements AccessInterface {
+
+  public function access(AccountInterface $account) {
+    return $account->hasPermission('administer pages') ? AccessResult::allowed() : AccessResult::forbidden();
+  }
+
+}
diff --git a/src/Controller/PageManagerController.php b/src/Controller/PageManagerController.php
index a0dc64f..13d0d78 100644
--- a/src/Controller/PageManagerController.php
+++ b/src/Controller/PageManagerController.php
@@ -15,6 +15,7 @@ use Drupal\Core\Url;
 use Drupal\ctools\Form\AjaxFormTrait;
 use Drupal\page_manager\PageInterface;
 use Drupal\page_manager\PageVariantInterface;
+use Drupal\user\SharedTempStoreFactory;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\HttpFoundation\Request;
 
@@ -54,6 +55,13 @@ class PageManagerController extends ControllerBase {
   protected $contextHandler;
 
   /**
+   * Tempstore factory.
+   *
+   * @var \Drupal\user\SharedTempStoreFactory
+   */
+  protected $tempstore;
+
+  /**
    * Constructs a new VariantPluginEditForm.
    *
    * @param \Drupal\Core\Block\BlockManagerInterface $block_manager
@@ -64,12 +72,15 @@ class PageManagerController extends ControllerBase {
    *   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) {
+  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;
   }
 
   /**
@@ -80,7 +91,8 @@ class PageManagerController extends ControllerBase {
       $container->get('plugin.manager.block'),
       $container->get('plugin.manager.condition'),
       $container->get('plugin.manager.display_variant'),
-      $container->get('context.handler')
+      $container->get('context.handler'),
+      $container->get('user.shared_tempstore')
     );
   }
 
@@ -93,7 +105,10 @@ class PageManagerController extends ControllerBase {
    * @return string
    *   The title for the page edit form.
    */
-  public function editPageTitle(PageInterface $page) {
+  public function editPageTitle($machine_name, $tempstore_id) {
+    $cached_values = $this->tempstore->get($tempstore_id)->get($machine_name);
+    /** @var \Drupal\page_manager\PageInterface $page */
+    $page = $cached_values['page'];
     return $this->t('Edit %label page', ['%label' => $page->label()]);
   }
 
@@ -277,7 +292,10 @@ class PageManagerController extends ControllerBase {
    * @return array
    *   The block selection page.
    */
-  public function selectBlock(Request $request, PageVariantInterface $page_variant) {
+  public function selectBlock(Request $request, $block_display) {
+    /** @var \Drupal\page_manager\Plugin\DisplayVariant\PageBlockDisplayVariant $variant_plugin */
+    $variant_plugin = $this->tempstore->get('page_manager.block_display')->get($block_display)['plugin'];
+
     // Add a section containing the available blocks to be added to the variant.
     $build = [
       '#type' => 'container',
@@ -287,7 +305,7 @@ class PageManagerController extends ControllerBase {
         ],
       ],
     ];
-    $available_plugins = $this->blockManager->getDefinitionsForContexts($page_variant->getContexts());
+    $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) {
@@ -306,11 +324,11 @@ class PageManagerController extends ControllerBase {
       // 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('page_manager.variant_add_block', [
-          'page' => $page_variant->get('page'),
-          'page_variant' => $page_variant->id(),
+        'url' => Url::fromRoute('page_manager.block_display_add_block', [
+          'block_display' => $block_display,
           'block_id' => $plugin_id,
           'region' => $request->query->get('region'),
+          'destination' => $request->query->get('destination'),
         ]),
         'attributes' => $this->getAjaxAttributes(),
       ];
diff --git a/src/Entity/Page.php b/src/Entity/Page.php
index fa1011e..185ccba 100644
--- a/src/Entity/Page.php
+++ b/src/Entity/Page.php
@@ -7,6 +7,9 @@
 
 namespace Drupal\page_manager\Entity;
 
+use Drupal\Core\Plugin\Context\Context;
+use Drupal\Core\Plugin\Context\ContextDefinition;
+use Drupal\Core\Plugin\Context\ContextInterface;
 use Drupal\page_manager\PageInterface;
 use Drupal\Core\Condition\ConditionPluginCollection;
 use Drupal\Core\Config\Entity\ConfigEntityBase;
@@ -23,9 +26,11 @@ use Drupal\page_manager\PageVariantInterface;
  *     "access" = "Drupal\page_manager\Entity\PageAccess",
  *     "list_builder" = "Drupal\page_manager\Entity\PageListBuilder",
  *     "form" = {
- *       "add" = "Drupal\page_manager\Form\PageAddForm",
- *       "edit" = "Drupal\page_manager\Form\PageEditForm",
- *       "delete" = "Drupal\page_manager\Form\PageDeleteForm",
+ *       "delete" = "Drupal\page_manager\Form\PageDeleteForm"
+ *     },
+ *     "wizard" = {
+ *       "add" = "Drupal\page_manager\Wizard\PageAddWizard",
+ *       "edit" = "Drupal\page_manager\Wizard\PageEditWizard"
  *     }
  *   },
  *   admin_permission = "administer pages",
@@ -37,6 +42,7 @@ use Drupal\page_manager\PageVariantInterface;
  *   config_export = {
  *     "id",
  *     "label",
+ *     "description",
  *     "use_admin_theme",
  *     "path",
  *     "access_logic",
@@ -46,7 +52,7 @@ use Drupal\page_manager\PageVariantInterface;
  *   links = {
  *     "collection" = "/admin/structure/page_manager",
  *     "add-form" = "/admin/structure/page_manager/add",
- *     "edit-form" = "/admin/structure/page_manager/manage/{page}",
+ *     "edit-form" = "/admin/structure/page_manager/manage/{machine_name}/{step}",
  *     "delete-form" = "/admin/structure/page_manager/manage/{page}/delete",
  *     "enable" = "/admin/structure/page_manager/manage/{page}/enable",
  *     "disable" = "/admin/structure/page_manager/manage/{page}/disable"
@@ -70,6 +76,13 @@ class Page extends ConfigEntityBase implements PageInterface {
   protected $label;
 
   /**
+   * The description of the page entity.
+   *
+   * @var string
+   */
+  protected $description;
+
+  /**
    * The path of the page entity.
    *
    * @var string
@@ -141,6 +154,13 @@ class Page extends ConfigEntityBase implements PageInterface {
   /**
    * {@inheritdoc}
    */
+  public function getDescription() {
+    return $this->description;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function getExecutable() {
     if (!isset($this->executable)) {
       $this->executable = $this->executableFactory()->get($this);
@@ -174,6 +194,13 @@ class Page extends ConfigEntityBase implements PageInterface {
    */
   public function postSave(EntityStorageInterface $storage, $update = TRUE) {
     parent::postSave($storage, $update);
+
+    // Save the page variants just in case they were modified (or added) and
+    // not saved independently.
+    foreach ($this->getVariants() as $page_variant) {
+      $page_variant->save();
+    }
+
     static::routeBuilder()->setRebuildNeeded();
   }
 
@@ -297,8 +324,21 @@ class Page extends ConfigEntityBase implements PageInterface {
   /**
    * {@inheritdoc}
    */
+  public static function preDelete(EntityStorageInterface $storage, array $entities) {
+    parent::preDelete($storage, $entities); // TODO: Change the autogenerated stub
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function addVariant(PageVariantInterface $variant) {
+    // If variants hasn't been initialized, we initialize it before adding the
+    // new variant.
+    if ($this->variants === NULL) {
+      $this->getVariants();
+    }
     $this->variants[$variant->id()] = $variant;
+    $this->sortVariants();
     return $this;
   }
 
@@ -331,23 +371,19 @@ class Page extends ConfigEntityBase implements PageInterface {
       foreach ($this->variantStorage()->loadByProperties(['page' => $this->id()]) as $variant) {
         $this->variants[$variant->id()] = $variant;
       }
-      // Suppress errors because of https://bugs.php.net/bug.php?id=50688.
-      @uasort($this->variants, [$this, 'variantSortHelper']);
+      $this->sortVariants();
     }
     return $this->variants;
   }
 
   /**
-   * {@inheritdoc}
+   * Sort variants.
    */
-  public function variantSortHelper($a, $b) {
-    $a_weight = $a->getWeight();
-    $b_weight = $b->getWeight();
-    if ($a_weight == $b_weight) {
-      return 0;
+  protected function sortVariants() {
+    if (isset($this->variants)) {
+      // Suppress errors because of https://bugs.php.net/bug.php?id=50688.
+      @uasort($this->variants, '\Drupal\page_manager\Entity\PageVariant::sort');
     }
-
-    return ($a_weight < $b_weight) ? -1 : 1;
   }
 
   /**
@@ -356,21 +392,8 @@ class Page extends ConfigEntityBase implements PageInterface {
   public function __sleep() {
     $vars = parent::__sleep();
 
-    // Avoid serializing plugin collections and the page executable as they
-    // might contain references to a lot of objects including the container.
-    $unset_vars = [
-      'variants' => NULL,
-      'accessConditionCollection' => 'access_variants',
-      'executable' => NULL,
-    ];
-    foreach ($unset_vars as $unset_var => $configuration_key) {
-      if (!empty($this->$unset_var)) {
-        if ($configuration_key) {
-          $this->set($configuration_key, $this->$unset_var->getConfiguration());
-        }
-        unset($vars[array_search($unset_var, $vars)]);
-      }
-    }
+    // Avoid serializing the page executable as it represents runtime state.
+    unset($vars['executable']);
 
     return $vars;
   }
diff --git a/src/Entity/PageListBuilder.php b/src/Entity/PageListBuilder.php
index 7a599dc..9248eba 100644
--- a/src/Entity/PageListBuilder.php
+++ b/src/Entity/PageListBuilder.php
@@ -40,6 +40,16 @@ class PageListBuilder extends ConfigEntityListBuilder {
   }
 
   /**
+   * {@inheritdoc}
+   */
+  public function getDefaultOperations(EntityInterface $entity) {
+    $operations = parent::getDefaultOperations($entity);
+    $operations['edit']['url'] = new Url('entity.page.edit_form', ['machine_name' => $entity->id(), 'step' => 'general']);
+
+    return $operations;
+  }
+
+  /**
    * Gets the displayable path of a page entity.
    *
    * @param \Drupal\page_manager\PageInterface $entity
diff --git a/src/Entity/PageVariant.php b/src/Entity/PageVariant.php
index b00e62d..547ec8a 100644
--- a/src/Entity/PageVariant.php
+++ b/src/Entity/PageVariant.php
@@ -13,6 +13,7 @@ use Drupal\Core\Entity\EntityStorageInterface;
 use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Plugin\DefaultSingleLazyPluginCollection;
 use Drupal\ctools\Plugin\ConditionVariantTrait;
+use Drupal\page_manager\PageInterface;
 use Drupal\page_manager\PageVariantInterface;
 
 /**
@@ -24,11 +25,6 @@ use Drupal\page_manager\PageVariantInterface;
  *   handlers = {
  *     "access" = "Drupal\page_manager\Entity\PageVariantAccess",
  *     "view_builder" = "Drupal\page_manager\Entity\PageVariantViewBuilder",
- *     "form" = {
- *       "add" = "Drupal\page_manager\Form\PageVariantAddForm",
- *       "edit" = "Drupal\page_manager\Form\PageVariantEditForm",
- *       "delete" = "Drupal\page_manager\Form\PageVariantDeleteForm"
- *     }
  *   },
  *   admin_permission = "administer pages",
  *   entity_keys = {
@@ -48,10 +44,6 @@ use Drupal\page_manager\PageVariantInterface;
  *     "selection_logic",
  *     "contexts"
  *   },
- *   links = {
- *     "edit-form" = "/admin/structure/page_manager/manage/{page}/variant/{page_variant}",
- *     "delete-form" = "/admin/structure/page_manager/manage/{page}/variant/{page_variant}/delete",
- *   },
  *   lookup_keys = {
  *     "page"
  *   }
@@ -111,6 +103,13 @@ class PageVariant extends ConfigEntityBase implements PageVariantInterface {
   protected $page;
 
   /**
+   * The page object for the parent page.
+   *
+   * @var \Drupal\page_manager\PageInterface
+   */
+  protected $parentPage;
+
+  /**
    * The plugin configuration for the selection criteria condition plugins.
    *
    * @var array
@@ -207,6 +206,9 @@ class PageVariant extends ConfigEntityBase implements PageVariantInterface {
    */
   protected function getVariantPluginCollection() {
     if (!$this->variantPluginCollection) {
+      if (empty($this->variant_settings['uuid'])) {
+        $this->variant_settings['uuid'] = $this->uuidGenerator()->generate();
+      }
       $this->variantPluginCollection = new DefaultSingleLazyPluginCollection(\Drupal::service('plugin.manager.display_variant'), $this->variant, $this->variant_settings);
     }
     return $this->variantPluginCollection;
@@ -232,17 +234,29 @@ class PageVariant extends ConfigEntityBase implements PageVariantInterface {
    * @return \Drupal\page_manager\Entity\Page
    */
   protected function getPage() {
-    if (!$this->page) {
+    $page = Page::load($this->page);
+    if (!$page) {
       throw new \UnexpectedValueException('The page variant has no associated page');
     }
-    return Page::load($this->page);
+    return $page;
   }
 
   /**
    * {@inheritdoc}
    */
   public function getContexts() {
-    return array_merge($this->getPage()->getContexts(), $this->contexts);
+    try {
+      $page = $this->getPage();
+    }
+    catch (\UnexpectedValueException $e) {
+      // This can happen adding a new page - it may only exist in the tempstore.
+      // @todo Remove once contexts are stored only on the variant!
+      $cached_values = $this->getTempstoreFactory()->get('page_manager.page')->get($this->page);
+      if (!empty($cached_values) && !empty($cached_values['page'])) {
+        $page = $cached_values['page'];
+      }
+    }
+    return array_merge($page->getContexts(), $this->contexts);
   }
 
   /**
@@ -318,4 +332,13 @@ class PageVariant extends ConfigEntityBase implements PageVariantInterface {
     return \Drupal::service('context.handler');
   }
 
+  /**
+   * Wraps the shared tempstore factory.
+   *
+   * @return \Drupal\user\SharedTempStoreFactory
+   */
+  protected function getTempstoreFactory() {
+    return \Drupal::service('user.shared_tempstore');
+  }
+
 }
diff --git a/src/Form/AccessConditionAddForm.php b/src/Form/AccessConditionAddForm.php
deleted file mode 100644
index b653424..0000000
--- a/src/Form/AccessConditionAddForm.php
+++ /dev/null
@@ -1,73 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\page_manager\Form\AccessConditionAddForm.
- */
-
-namespace Drupal\page_manager\Form;
-
-use Drupal\Core\Condition\ConditionManager;
-use Symfony\Component\DependencyInjection\ContainerInterface;
-
-/**
- * Provides a form for adding a new access condition.
- */
-class AccessConditionAddForm extends AccessConditionFormBase {
-
-  /**
-   * The condition manager.
-   *
-   * @var \Drupal\Core\Condition\ConditionManager
-   */
-  protected $conditionManager;
-
-  /**
-   * Constructs a new AccessConditionAddForm.
-   *
-   * @param \Drupal\Core\Condition\ConditionManager $condition_manager
-   *   The condition manager.
-   */
-  public function __construct(ConditionManager $condition_manager) {
-    $this->conditionManager = $condition_manager;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function create(ContainerInterface $container) {
-    return new static(
-      $container->get('plugin.manager.condition')
-    );
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getFormId() {
-    return 'page_manager_access_condition_add_form';
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function prepareCondition($condition_id) {
-    // Create a new access condition instance.
-    return $this->conditionManager->createInstance($condition_id);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function submitButtonText() {
-    return $this->t('Add access condition');
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function submitMessageText() {
-    return $this->t('The %label access condition has been added.', ['%label' => $this->condition->getPluginDefinition()['label']]);
-  }
-
-}
diff --git a/src/Form/AccessConditionDeleteForm.php b/src/Form/AccessConditionDeleteForm.php
deleted file mode 100644
index f20e14d..0000000
--- a/src/Form/AccessConditionDeleteForm.php
+++ /dev/null
@@ -1,80 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\page_manager\Form\AccessConditionDeleteForm.
- */
-
-namespace Drupal\page_manager\Form;
-
-use Drupal\Core\Form\FormStateInterface;
-use Drupal\page_manager\PageInterface;
-use Drupal\Core\Form\ConfirmFormBase;
-
-/**
- * Provides a form for deleting an access condition.
- */
-class AccessConditionDeleteForm extends ConfirmFormBase {
-
-  /**
-   * The page entity this selection condition belongs to.
-   *
-   * @var \Drupal\page_manager\PageInterface
-   */
-  protected $page;
-
-  /**
-   * The access condition used by this form.
-   *
-   * @var \Drupal\Core\Condition\ConditionInterface
-   */
-  protected $accessCondition;
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getFormId() {
-    return 'page_manager_access_condition_delete_form';
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getQuestion() {
-    return $this->t('Are you sure you want to delete the access condition %name?', ['%name' => $this->accessCondition->getPluginDefinition()['label']]);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getCancelUrl() {
-    return $this->page->toUrl('edit-form');
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getConfirmText() {
-    return $this->t('Delete');
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function buildForm(array $form, FormStateInterface $form_state, PageInterface $page = NULL, $condition_id = NULL) {
-    $this->page = $page;
-    $this->accessCondition = $page->getAccessCondition($condition_id);
-    return parent::buildForm($form, $form_state);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function submitForm(array &$form, FormStateInterface $form_state) {
-    $this->page->removeAccessCondition($this->accessCondition->getConfiguration()['uuid']);
-    $this->page->save();
-    drupal_set_message($this->t('The access condition %name has been removed.', ['%name' => $this->accessCondition->getPluginDefinition()['label']]));
-    $form_state->setRedirectUrl($this->getCancelUrl());
-  }
-
-}
diff --git a/src/Form/AccessConditionEditForm.php b/src/Form/AccessConditionEditForm.php
deleted file mode 100644
index 4389b60..0000000
--- a/src/Form/AccessConditionEditForm.php
+++ /dev/null
@@ -1,44 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\page_manager\Form\AccessConditionEditForm.
- */
-
-namespace Drupal\page_manager\Form;
-
-/**
- * Provides a form for editing an access condition.
- */
-class AccessConditionEditForm extends AccessConditionFormBase {
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getFormId() {
-    return 'page_manager_access_condition_edit_form';
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function prepareCondition($condition_id) {
-    // Load the access condition directly from the page entity.
-    return $this->page->getAccessCondition($condition_id);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function submitButtonText() {
-    return $this->t('Update access condition');
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function submitMessageText() {
-    return $this->t('The %label access condition has been updated.', ['%label' => $this->condition->getPluginDefinition()['label']]);
-  }
-
-}
diff --git a/src/Form/AccessConditionFormBase.php b/src/Form/AccessConditionFormBase.php
deleted file mode 100644
index 7535407..0000000
--- a/src/Form/AccessConditionFormBase.php
+++ /dev/null
@@ -1,51 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\page_manager\Form\AccessConditionFormBase.
- */
-
-namespace Drupal\page_manager\Form;
-
-use Drupal\Core\Form\FormStateInterface;
-use Drupal\page_manager\PageInterface;
-
-/**
- * Provides a base form for editing and adding an access condition.
- */
-abstract class AccessConditionFormBase extends ConditionFormBase {
-
-  /**
-   * The page entity this condition belongs to.
-   *
-   * @var \Drupal\page_manager\PageInterface
-   */
-  protected $page;
-
-  /**
-   * {@inheritdoc}
-   */
-  public function buildForm(array $form, FormStateInterface $form_state, PageInterface $page = NULL, $condition_id = NULL) {
-    $this->page = $page;
-    return parent::buildForm($form, $form_state, $condition_id, $page->getContexts());
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function submitForm(array &$form, FormStateInterface $form_state) {
-    parent::submitForm($form, $form_state);
-
-    $configuration = $this->condition->getConfiguration();
-    // If this access condition is new, add it to the page.
-    if (!isset($configuration['uuid'])) {
-      $this->page->addAccessCondition($configuration);
-    }
-
-    // Save the page entity.
-    $this->page->save();
-
-    $form_state->setRedirectUrl($this->page->toUrl('edit-form'));
-  }
-
-}
diff --git a/src/Form/AccessConfigure.php b/src/Form/AccessConfigure.php
new file mode 100644
index 0000000..11d4e71
--- /dev/null
+++ b/src/Form/AccessConfigure.php
@@ -0,0 +1,58 @@
+<?php
+/**
+ * @file
+ * Contains \Drupal\page_manager\Form\AccessConfigure;
+ */
+
+namespace Drupal\page_manager\Form;
+
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\ctools\Form\ConditionConfigure;
+use Drupal\page_manager\PageInterface;
+
+class AccessConfigure extends ConditionConfigure {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getParentRouteInfo($cached_values) {
+    /** @var $page \Drupal\page_manager\PageInterface */
+    $page = $cached_values['page'];
+
+    $route_name = $page->isNew() ? 'entity.page.add_step_form' : 'entity.page.edit_form';
+    return [$route_name, [
+      'machine_name' => $this->machine_name,
+      'step' => 'access',
+    ]];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getConditions($cached_values) {
+    /** @var $page \Drupal\page_manager\PageInterface */
+    $page = $cached_values['page'];
+    return $page->get('access_conditions');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setConditions($cached_values, $conditions) {
+    /** @var $page \Drupal\page_manager\PageInterface */
+    $page = $cached_values['page'];
+    $page->set('access_conditions', $conditions);
+    $cached_values['page'] = $page;
+    return $cached_values;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getContexts($cached_values) {
+    /** @var $page \Drupal\page_manager\PageInterface */
+    $page = $cached_values['page'];
+    return $page->getContexts();
+  }
+
+}
diff --git a/src/Form/AccessDelete.php b/src/Form/AccessDelete.php
new file mode 100644
index 0000000..397159c
--- /dev/null
+++ b/src/Form/AccessDelete.php
@@ -0,0 +1,66 @@
+<?php
+/**
+ * @file
+ * Contains \Drupal\page_manager\Form\AccessDelete.
+ */
+
+namespace Drupal\page_manager\Form;
+
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\ctools\Form\ConditionDelete;
+use Drupal\page_manager\PageInterface;
+
+class AccessDelete extends ConditionDelete {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getParentRouteInfo($cached_values) {
+    /** @var $page \Drupal\page_manager\PageInterface */
+    $page = $cached_values['page'];
+
+    $route_name = $page->isNew() ? 'entity.page.add_step_form' : 'entity.page.edit_form';
+    return [$route_name, [
+      'machine_name' => $this->machine_name,
+      'step' => 'access',
+    ]];
+  }
+
+  /**
+   * ConditionDelete puts this function on #validate but it doesn't exist.
+   *
+   * @todo: Remove when #2640392 is fixed.
+   */
+  public function validate($form, FormStateInterface $form_state) {
+    // Do nothing.
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getConditions($cached_values) {
+    /** @var $page \Drupal\page_manager\PageInterface */
+    $page = $cached_values['page'];
+    return $page->get('access_conditions');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setConditions($cached_values, $conditions) {
+    /** @var $page \Drupal\page_manager\PageInterface */
+    $page = $cached_values['page'];
+    $page->set('access_conditions', $conditions);
+    return $cached_values;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getContexts($cached_values) {
+    /** @var $page \Drupal\page_manager\PageInterface */
+    $page = $cached_values['page'];
+    return $page->getContexts();
+  }
+
+}
diff --git a/src/Form/ConditionFormBase.php b/src/Form/ConditionFormBase.php
deleted file mode 100644
index 4d0528e..0000000
--- a/src/Form/ConditionFormBase.php
+++ /dev/null
@@ -1,110 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\page_manager\Form\ConditionFormBase.
- */
-
-namespace Drupal\page_manager\Form;
-
-use Drupal\Core\Form\FormState;
-use Drupal\Core\Form\FormStateInterface;
-use Drupal\Core\Form\FormBase;
-use Drupal\Core\Plugin\ContextAwarePluginInterface;
-use Drupal\Core\Plugin\ContextAwarePluginAssignmentTrait;
-
-/**
- * Provides a base form for editing and adding a condition.
- */
-abstract class ConditionFormBase extends FormBase {
-
-  use ContextAwarePluginAssignmentTrait;
-
-  /**
-   * The condition used by this form.
-   *
-   * @var \Drupal\Core\Condition\ConditionInterface
-   */
-  protected $condition;
-
-  /**
-   * Prepares the condition used by this form.
-   *
-   * @param string $condition_id
-   *   Either a condition ID, or the plugin ID used to create a new
-   *   condition.
-   *
-   * @return \Drupal\Core\Condition\ConditionInterface
-   *   The condition object.
-   */
-  abstract protected function prepareCondition($condition_id);
-
-  /**
-   * Returns the text to use for the submit button.
-   *
-   * @return string
-   *   The submit button text.
-   */
-  abstract protected function submitButtonText();
-
-  /**
-   * Returns the text to use for the submit message.
-   *
-   * @return string
-   *   The submit message text.
-   */
-  abstract protected function submitMessageText();
-
-  /**
-   * {@inheritdoc}
-   */
-  public function buildForm(array $form, FormStateInterface $form_state, $condition_id = NULL, $contexts = []) {
-    $this->condition = $this->prepareCondition($condition_id);
-    $temporary = $form_state->getTemporary();
-    $temporary['gathered_contexts'] = $contexts;
-    $form_state->setTemporary($temporary);
-
-    // Allow the condition to add to the form.
-    $form['condition'] = $this->condition->buildConfigurationForm([], $form_state);
-    $form['condition']['#tree'] = TRUE;
-
-    $form['actions'] = ['#type' => 'actions'];
-    $form['actions']['submit'] = [
-      '#type' => 'submit',
-      '#value' => $this->submitButtonText(),
-      '#button_type' => 'primary',
-    ];
-
-    return $form;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function validateForm(array &$form, FormStateInterface $form_state) {
-    // Allow the condition to validate the form.
-    $condition_values = (new FormState())->setValues($form_state->getValue('condition'));
-    $this->condition->validateConfigurationForm($form, $condition_values);
-    // Update the original form values.
-    $form_state->setValue('condition', $condition_values->getValues());
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function submitForm(array &$form, FormStateInterface $form_state) {
-    // Allow the condition to submit the form.
-    $condition_values = (new FormState())->setValues($form_state->getValue('condition'));
-    $this->condition->submitConfigurationForm($form, $condition_values);
-    // Update the original form values.
-    $form_state->setValue('condition', $condition_values->getValues());
-
-    if ($this->condition instanceof ContextAwarePluginInterface) {
-      $this->condition->setContextMapping($condition_values->getValue('context_mapping', []));
-    }
-
-    // Set the submission message.
-    drupal_set_message($this->submitMessageText());
-  }
-
-}
diff --git a/src/Form/PageAccessForm.php b/src/Form/PageAccessForm.php
new file mode 100644
index 0000000..a180020
--- /dev/null
+++ b/src/Form/PageAccessForm.php
@@ -0,0 +1,68 @@
+<?php
+/**
+ * @file
+ * Contains \Drupal\page_manager\Form\PageAccessForm.
+ */
+
+namespace Drupal\page_manager\Form;
+
+
+use Drupal\ctools\Form\ManageConditions;
+
+class PageAccessForm extends ManageConditions {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'page_manager_access_form';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getConditionClass() {
+    return 'Drupal\page_manager\Form\AccessConfigure';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getTempstoreId() {
+    return 'page_manager.page';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getOperationsRouteInfo($cached_values, $machine_name, $row) {
+    return ['entity.page.condition', ['machine_name' => $machine_name, 'condition' => $row]];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getConditions($cached_values) {
+    /** @var $page \Drupal\page_manager\Entity\Page */
+    $page = $cached_values['page'];
+    return $page->get('access_conditions');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getContexts($cached_values) {
+    /** @var $page \Drupal\page_manager\Entity\Page */
+    $page = $cached_values['page'];
+    return $page->getContexts();
+  }
+
+  /**
+   * The route to which condition 'add' actions should submit.
+   *
+   * @return string
+   */
+  protected function getAddRoute($cached_values) {
+    return 'entity.page.condition.add';
+  }
+}
diff --git a/src/Form/PageAddForm.php b/src/Form/PageAddForm.php
deleted file mode 100644
index 62f74bb..0000000
--- a/src/Form/PageAddForm.php
+++ /dev/null
@@ -1,28 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\page_manager\Form\PageAddForm.
- */
-
-namespace Drupal\page_manager\Form;
-
-use Drupal\Core\Form\FormStateInterface;
-
-/**
- * Provides a form for adding a new page entity.
- */
-class PageAddForm extends PageFormBase {
-
-  /**
-   * {@inheritdoc}
-   */
-  public function save(array $form, FormStateInterface $form_state) {
-    parent::save($form, $form_state);
-    drupal_set_message($this->t('The %label page has been added.', ['%label' => $this->entity->label()]));
-    $form_state->setRedirect('entity.page.edit_form', [
-      'page' => $this->entity->id(),
-    ]);
-  }
-
-}
diff --git a/src/Form/PageContextsForm.php b/src/Form/PageContextsForm.php
new file mode 100644
index 0000000..48c1eb0
--- /dev/null
+++ b/src/Form/PageContextsForm.php
@@ -0,0 +1,89 @@
+<?php
+/**
+ * @file
+ * Contains \Drupal\page_manager\Form\PageContextsForm.
+ */
+
+namespace Drupal\page_manager\Form;
+
+use Drupal\Core\Form\FormBase;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Plugin\Context\ContextDefinition;
+use Drupal\Core\Plugin\Context\ContextDefinitionInterface;
+use Drupal\Core\TypedData\ComplexDataDefinitionInterface;
+use Drupal\Core\TypedData\ListDataDefinitionInterface;
+
+class PageContextsForm extends FormBase {
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'page_manager_variant_context_form';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state) {
+    $cached_values = $form_state->getTemporaryValue('wizard');
+    $form['items'] = [
+      '#type' => 'markup',
+      '#prefix' => '<div id="available-contexts">',
+      '#suffix' => '</div>',
+      '#theme' => 'table',
+      '#header' => [$this->t('Context'), $this->t('Type'), $this->t('Operations')],
+      '#rows' => $this->renderRows($cached_values),
+      '#empty' => $this->t('No Contexts configured for this variant.')
+    ];
+    $form['#attached']['library'][] = 'core/drupal.dialog.ajax';
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    $cached_values = $form_state->getTemporaryValue('wizard');
+    /** @var $page \Drupal\page_manager\Entity\PageVariant */
+    $page_variant = $cached_values['page_variant'];
+  }
+
+  protected function renderRows($cached_values) {
+    $contexts = [];
+    /** @var $page \Drupal\page_manager\Entity\Page */
+    $page = $cached_values['page'];
+    foreach ($page->getContexts() as $parameter => $context) {
+      /*$build = array(
+        '#type' => 'operations',
+        '#links' => $this->getOperations('page_manager.route.parameters.configure', $cached_values, $row),
+      );*/
+      /** @var $definition \Drupal\Core\Plugin\Context\ContextDefinitionInterface */
+      $definition = $context->getContextDefinition();
+      $data_definition = $definition->getDataDefinition();
+      if ($data_definition instanceof ComplexDataDefinitionInterface) {
+        foreach ($data_definition->getPropertyDefinitions() as $property => $value) {
+          if ($value instanceof ListDataDefinitionInterface) {
+            //drupal_set_message('List ' . $property . ': ' . var_export($value->getItemDefinition()->getDataType(), TRUE));
+            //drupal_set_message(var_export($value->getItemDefinition(), TRUE));
+            $definition = \Drupal::typedDataManager()->create($value->getItemDefinition());
+            //drupal_set_message(var_export($definition, TRUE));
+          }
+          else {
+            //drupal_set_message('Primative ' . $property . ': ' . var_export($value->getDataType(), TRUE));
+          }
+        }
+      }
+
+      $contexts[] = [
+        $context->getContextDefinition()->getLabel(),
+        $context->getContextDefinition()->getDataType(),
+        ''
+      ];
+    }
+    return $contexts;
+  }
+
+  protected function getOperations(ContextDefinitionInterface $context) {
+
+  }
+}
\ No newline at end of file
diff --git a/src/Form/PageEditForm.php b/src/Form/PageEditForm.php
deleted file mode 100644
index 62f7242..0000000
--- a/src/Form/PageEditForm.php
+++ /dev/null
@@ -1,273 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\page_manager\Form\PageEditForm.
- */
-
-namespace Drupal\page_manager\Form;
-
-use Drupal\Core\Entity\EntityInterface;
-use Drupal\Core\Form\FormStateInterface;
-use Drupal\Core\Url;
-use Drupal\ctools\Form\AjaxFormTrait;
-
-/**
- * Provides a form for editing a page entity.
- */
-class PageEditForm extends PageFormBase {
-
-  use AjaxFormTrait;
-
-  /**
-   * {@inheritdoc}
-   */
-  public function form(array $form, FormStateInterface $form_state) {
-    $form = parent::form($form, $form_state);
-
-    $form['use_admin_theme'] = [
-      '#type' => 'checkbox',
-      '#title' => $this->t('Use admin theme'),
-      '#default_value' => $this->entity->usesAdminTheme(),
-    ];
-    $attributes = $this->getAjaxAttributes();
-    $add_button_attributes = $this->getAjaxButtonAttributes();
-
-    $form['context'] = [
-      '#type' => 'details',
-      '#title' => $this->t('Available context'),
-      '#open' => TRUE,
-    ];
-    $form['context']['add'] = [
-      '#type' => 'link',
-      '#title' => $this->t('Add new static context'),
-      '#url' => Url::fromRoute('page_manager.static_context_add', [
-        'page' => $this->entity->id(),
-      ]),
-      '#attributes' => $add_button_attributes,
-      '#attached' => [
-        'library' => [
-          'core/drupal.ajax',
-        ],
-      ],
-    ];
-    $form['context']['available_context'] = [
-      '#type' => 'table',
-      '#header' => [
-        $this->t('Label'),
-        $this->t('Name'),
-        $this->t('Type'),
-        $this->t('Operations'),
-      ],
-      '#empty' => $this->t('There is no available context.'),
-    ];
-    $contexts = $this->entity->getContexts();
-    foreach ($contexts as $name => $context) {
-      $context_definition = $context->getContextDefinition();
-
-      $row = [];
-      $row['label'] = [
-        '#markup' => $context_definition->getLabel(),
-      ];
-      $row['machine_name'] = [
-        '#markup' => $name,
-      ];
-      $row['type'] = [
-        '#markup' => $context_definition->getDataType(),
-      ];
-
-      // Add operation links if the context is a static context.
-      $operations = [];
-      if ($this->entity->getStaticContext($name)) {
-        $operations['edit'] = [
-          'title' => $this->t('Edit'),
-          'url' => Url::fromRoute('page_manager.static_context_edit', [
-            'page' => $this->entity->id(),
-            'name' => $name,
-          ]),
-          'attributes' => $attributes,
-        ];
-        $operations['delete'] = [
-          'title' => $this->t('Delete'),
-          'url' => Url::fromRoute('page_manager.static_context_delete', [
-            'page' => $this->entity->id(),
-            'name' => $name,
-          ]),
-          'attributes' => $attributes,
-        ];
-      }
-      $row['operations'] = [
-        '#type' => 'operations',
-        '#links' => $operations,
-      ];
-
-      $form['context']['available_context'][$name] = $row;
-    }
-
-    $form['variant_section'] = [
-      '#type' => 'details',
-      '#title' => $this->t('Variants'),
-      '#open' => TRUE,
-    ];
-    $form['variant_section']['add_new_page'] = [
-      '#type' => 'link',
-      '#title' => $this->t('Add new variant'),
-      '#url' => Url::fromRoute('page_manager.variant_select', [
-        'page' => $this->entity->id(),
-      ]),
-      '#attributes' => $add_button_attributes,
-      '#attached' => [
-        'library' => [
-          'core/drupal.ajax',
-        ],
-      ],
-    ];
-    $form['variant_section']['variants'] = [
-      '#type' => 'table',
-      '#header' => [
-        $this->t('Label'),
-        $this->t('Plugin'),
-        $this->t('Weight'),
-        $this->t('Operations'),
-      ],
-      '#empty' => $this->t('There are no variants.'),
-      '#tabledrag' => [[
-        'action' => 'order',
-        'relationship' => 'sibling',
-        'group' => 'variant-weight',
-      ]],
-    ];
-    /** @var \Drupal\page_manager\PageVariantInterface $page_variant */
-    foreach ($this->entity->getVariants() as $page_variant) {
-      $row = [
-        '#attributes' => [
-          'class' => ['draggable'],
-        ],
-      ];
-      $row['label']['#markup'] = $page_variant->label();
-      $row['id']['#markup'] = $page_variant->getVariantPlugin()->adminLabel();
-      $row['weight'] = [
-        '#type' => 'weight',
-        '#default_value' => $page_variant->getWeight(),
-        '#title' => $this->t('Weight for @page_variant variant', ['@page_variant' => $page_variant->label()]),
-        '#title_display' => 'invisible',
-        '#attributes' => [
-          'class' => ['variant-weight'],
-        ],
-      ];
-      $operations = [];
-      $operations['edit'] = [
-        'title' => $this->t('Edit'),
-        'url' => $page_variant->toUrl('edit-form'),
-      ];
-      $operations['delete'] = [
-        'title' => $this->t('Delete'),
-        'url' => $page_variant->toUrl('delete-form'),
-      ];
-      $row['operations'] = [
-        '#type' => 'operations',
-        '#links' => $operations,
-      ];
-      $form['variant_section']['variants'][$page_variant->id()] = $row;
-    }
-
-    if ($access_conditions = $this->entity->getAccessConditions()) {
-      $form['access_section_section'] = [
-        '#type' => 'details',
-        '#title' => $this->t('Access Conditions'),
-        '#open' => TRUE,
-      ];
-      $form['access_section_section']['add'] = [
-        '#type' => 'link',
-        '#title' => $this->t('Add new access condition'),
-        '#url' => Url::fromRoute('page_manager.access_condition_select', [
-          'page' => $this->entity->id(),
-        ]),
-        '#attributes' => $add_button_attributes,
-        '#attached' => [
-          'library' => [
-            'core/drupal.ajax',
-          ],
-        ],
-      ];
-      $form['access_section_section']['access_section'] = [
-        '#type' => 'table',
-        '#header' => [
-          $this->t('Label'),
-          $this->t('Description'),
-          $this->t('Operations'),
-        ],
-        '#empty' => $this->t('There are no access conditions.'),
-      ];
-
-      $form['access_section_section']['access_logic'] = [
-        '#type' => 'radios',
-        '#options' => [
-          'and' => $this->t('All conditions must pass'),
-          'or' => $this->t('Only one condition must pass'),
-        ],
-        '#default_value' => $this->entity->getAccessLogic(),
-      ];
-
-      $form['access_section_section']['access'] = [
-        '#tree' => TRUE,
-      ];
-      foreach ($access_conditions as $access_id => $access_condition) {
-        $row = [];
-        $row['label']['#markup'] = $access_condition->getPluginDefinition()['label'];
-        $row['description']['#markup'] = $access_condition->summary();
-        $operations = [];
-        $operations['edit'] = [
-          'title' => $this->t('Edit'),
-          'url' => Url::fromRoute('page_manager.access_condition_edit', [
-            'page' => $this->entity->id(),
-            'condition_id' => $access_id,
-          ]),
-          'attributes' => $attributes,
-        ];
-        $operations['delete'] = [
-          'title' => $this->t('Delete'),
-          'url' => Url::fromRoute('page_manager.access_condition_delete', [
-            'page' => $this->entity->id(),
-            'condition_id' => $access_id,
-          ]),
-          'attributes' => $attributes,
-        ];
-        $row['operations'] = [
-          '#type' => 'operations',
-          '#links' => $operations,
-        ];
-        $form['access_section_section']['access_section'][$access_id] = $row;
-      }
-    }
-    return $form;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function save(array $form, FormStateInterface $form_state) {
-    if (!$form_state->isValueEmpty('variants')) {
-      foreach ($form_state->getValue('variants') as $variant_id => $data) {
-        if ($variant_entity = $this->entity->getVariant($variant_id)) {
-          $variant_entity->setWeight($data['weight']);
-          $variant_entity->save();
-        }
-      }
-    }
-    parent::save($form, $form_state);
-    drupal_set_message($this->t('The %label page has been updated.', ['%label' => $this->entity->label()]));
-    $form_state->setRedirect('entity.page.collection');
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function copyFormValuesToEntity(EntityInterface $entity, array $form, FormStateInterface $form_state) {
-    // Variants will be handled independently.
-    $variants = $form_state->getValue('variants');
-    $form_state->unsetValue('variants');
-    parent::copyFormValuesToEntity($entity, $form, $form_state);
-    $form_state->setValue('variants', $variants);
-  }
-}
diff --git a/src/Form/PageFormBase.php b/src/Form/PageFormBase.php
deleted file mode 100644
index ae20c31..0000000
--- a/src/Form/PageFormBase.php
+++ /dev/null
@@ -1,120 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\page_manager\Form\PageFormBase.
- */
-
-namespace Drupal\page_manager\Form;
-
-use Drupal\Core\Entity\EntityForm;
-use Drupal\Core\Entity\Query\QueryFactory;
-use Drupal\Core\Form\FormStateInterface;
-use Symfony\Component\DependencyInjection\ContainerInterface;
-
-/**
- * Provides a base form for editing and adding a page entity.
- */
-abstract class PageFormBase extends EntityForm {
-
-  /**
-   * {@inheritdoc}
-   *
-   * @var \Drupal\page_manager\PageInterface
-   */
-  protected $entity;
-
-  /**
-   * The entity query factory.
-   *
-   * @var \Drupal\Core\Entity\Query\QueryFactory
-   */
-  protected $entityQuery;
-
-  /**
-   * Construct a new PageFormBase.
-   *
-   * @param \Drupal\Core\Entity\Query\QueryFactory $entity_query
-   *   The entity query factory.
-   */
-  public function __construct(QueryFactory $entity_query) {
-    $this->entityQuery = $entity_query;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function create(ContainerInterface $container) {
-    return new static(
-      $container->get('entity.query')
-    );
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function form(array $form, FormStateInterface $form_state) {
-    $form['label'] = [
-      '#type' => 'textfield',
-      '#title' => $this->t('Label'),
-      '#description' => $this->t('The label for this page.'),
-      '#default_value' => $this->entity->label(),
-      '#required' => TRUE,
-      '#maxlength' => '255',
-    ];
-    $form['id'] = [
-      '#type' => 'machine_name',
-      '#default_value' => $this->entity->id(),
-      '#disabled' => !$this->entity->isNew(),
-      '#maxlength' => 64,
-      '#required' => TRUE,
-      '#machine_name' => [
-        'exists' => [$this, 'exists'],
-      ],
-    ];
-    $form['path'] = [
-      '#type' => 'textfield',
-      '#title' => $this->t('Path'),
-      '#maxlength' => 255,
-      '#default_value' => $this->entity->getPath(),
-      '#required' => TRUE,
-      '#element_validate' => [[$this, 'validatePath']],
-    ];
-
-    return parent::form($form, $form_state);
-  }
-
-  /**
-   * Determines if the page entity already exists.
-   *
-   * @param string $id
-   *   The page entity ID.
-   *
-   * @return bool
-   *   TRUE if the format exists, FALSE otherwise.
-   */
-  public function exists($id) {
-    return (bool) $this->entityQuery->get('page')
-      ->condition('id', $id)
-      ->execute();
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function validatePath(&$element, FormStateInterface $form_state) {
-    // Ensure the path has a leading slash.
-    $value = '/' . trim($element['#value'], '/');
-    $form_state->setValueForElement($element, $value);
-
-    // Ensure each path is unique.
-    $path = $this->entityQuery->get('page')
-      ->condition('path', $value)
-      ->condition('id', $form_state->getValue('id'), '<>')
-      ->execute();
-    if ($path) {
-      $form_state->setErrorByName('path', $this->t('The page path must be unique.'));
-    }
-  }
-
-}
diff --git a/src/Form/PageGeneralForm.php b/src/Form/PageGeneralForm.php
new file mode 100644
index 0000000..2f4c427
--- /dev/null
+++ b/src/Form/PageGeneralForm.php
@@ -0,0 +1,178 @@
+<?php
+/**
+ * @file
+ * Contains \Drupal\page_manager\Form\PageGeneralForm.
+ */
+
+namespace Drupal\page_manager\Form;
+
+
+use Drupal\Core\Entity\Query\QueryFactory;
+use Drupal\Core\Display\VariantManager;
+use Drupal\Core\Form\FormBase;
+use Drupal\Core\Form\FormStateInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+class PageGeneralForm extends FormBase {
+
+  /**
+   * The variant manager.
+   *
+   * @var \Drupal\Core\Display\VariantManager
+   */
+  protected $variantManager;
+
+  /**
+   * The entity query factory.
+   *
+   * @var \Drupal\Core\Entity\Query\QueryFactory
+   */
+  protected $entityQuery;
+
+  /**
+   * Constructs a new PageGeneralForm.
+   *
+   * @param \Drupal\Core\Display\VariantManager $variant_manager
+   *   The variant manager.
+   * @param \Drupal\Core\Entity\Query\QueryFactory $entity_query
+   *   The entity query factory.
+   */
+  public function __construct(VariantManager $variant_manager, QueryFactory $entity_query) {
+    $this->variantManager = $variant_manager;
+    $this->entityQuery = $entity_query;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('plugin.manager.display_variant'),
+      $container->get('entity.query')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'page_manager_general_form';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state) {
+    $cached_values = $form_state->getTemporaryValue('wizard');
+    /** @var $page \Drupal\page_manager\Entity\Page */
+    $page = $cached_values['page'];
+    $form['description'] = [
+      '#type' => 'textarea',
+      '#title' => $this->t('Administrative description'),
+      '#default_value' => $page->getDescription(),
+    ];
+    $form['path'] = [
+      '#type' => 'textfield',
+      '#title' => $this->t('Path'),
+      '#maxlength' => 255,
+      '#default_value' => $page->getPath(),
+      '#required' => TRUE,
+      '#element_validate' => [[$this, 'validatePath']],
+    ];
+    $form['use_admin_theme'] = [
+      '#type' => 'checkbox',
+      '#title' => $this->t('Use admin theme'),
+      '#default_value' => $page->usesAdminTheme(),
+    ];
+
+    if ($page->isNew()) {
+      $variant_plugin_options = [];
+      foreach ($this->variantManager->getDefinitions() as $plugin_id => $definition) {
+        $variant_plugin_options[$plugin_id] = $definition['admin_label'];
+      }
+      $form['variant_plugin_id'] = [
+        '#title' => $this->t('Variant type'),
+        '#type' => 'select',
+        '#options' => $variant_plugin_options,
+        '#default_value' => !empty($cached_values['variant_plugin_id']) ? $cached_values['variant_plugin_id'] : '',
+      ];
+      $form['wizard_options'] = [
+        '#type' => 'checkboxes',
+        '#title' => $this->t('Optional features'),
+        '#description' => $this->t('Check any optional features you need to be presented with forms for configuring them. If you do not check them here you will still be able to utilize these features once the new page is created. If you are not sure, leave these unchecked.'),
+        '#options' => [
+          'access' => $this->t('Access controls'),
+          'selection' => $this->t('Selection rules'),
+          // @todo must complete the contexts section of the wizard.
+          //'contexts' => $this->t('Contexts'),
+        ],
+        '#default_value' => !empty($cached_values['wizard_options']) ? $cached_values['wizard_options'] : [],
+      ];
+    }
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    $cached_values = $form_state->getTemporaryValue('wizard');
+    /** @var $page \Drupal\page_manager\Entity\Page */
+    $page = $cached_values['page'];
+    $page->set('description', $form_state->getValue('description'));
+    $page->set('path', $form_state->getValue('path'));
+    $page->set('use_admin_theme', $form_state->getValue('use_admin_theme'));
+
+    if ($page->isNew()) {
+      $page->set('id', $form_state->getValue('id'));
+      $page->set('label', $form_state->getValue('label'));
+      if (empty($cached_values['variant_plugin_id'])) {
+        $variant_plugin_id = $cached_values['variant_plugin_id'] = $form_state->getValue('variant_plugin_id');
+        $cached_values['page_variant'] = \Drupal::entityManager()
+          ->getStorage('page_variant')
+          ->create([
+            'variant' => $form_state->getValue('variant_plugin_id'),
+            'page' => $page->id(),
+            'id' => "{$page->id()}-{$variant_plugin_id}-0",
+            'label' => $form['variant_plugin_id']['#options'][$variant_plugin_id],
+          ]);
+        $page->addVariant($cached_values['page_variant']);
+      }
+      if ($cached_values['variant_plugin_id'] != $form_state->getValue('variant_plugin_id') && !empty($cached_values['page_variant'])) {
+        $page_variant = $cached_values['page_variant'];
+        /** @var $page_variant \Drupal\page_manager\Entity\PageVariant */
+        $page_variant->set('variant', $form_state->getValue('variant_plugin_id'));
+        $page_variant->set('variant_settings', []);
+        $cached_values['variant_plugin_id'] = $form_state->getValue('variant_plugin_id');
+      }
+
+      $cached_values['wizard_options'] = $form_state->getValue('wizard_options');
+      $form_state->setTemporaryValue('wizard', $cached_values);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validatePath(&$element, FormStateInterface $form_state) {
+    $cached_values = $form_state->getTemporaryValue('wizard');
+    /** @var $page \Drupal\page_manager\Entity\Page */
+    $page = $cached_values['page'];
+
+    // Ensure the path has a leading slash.
+    $value = '/' . trim($element['#value'], '/');
+    $form_state->setValueForElement($element, $value);
+
+    // Ensure each path is unique.
+    $path_query = $this->entityQuery->get('page')
+      ->condition('path', $value);
+    if (!$page->isNew()) {
+      $path_query->condition('id', $page->id(), '<>');
+    }
+    $path = $path_query->execute();
+    if ($path) {
+      $form_state->setErrorByName('path', $this->t('The page path must be unique.'));
+    }
+  }
+
+}
diff --git a/src/Form/PageReorderVariantsForm.php b/src/Form/PageReorderVariantsForm.php
new file mode 100644
index 0000000..37fb295
--- /dev/null
+++ b/src/Form/PageReorderVariantsForm.php
@@ -0,0 +1,138 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\page_manager\Form\PageReorderVariantsForm.
+ */
+
+namespace Drupal\page_manager\Form;
+
+use Drupal\Core\Form\FormBase;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\page_manager\Entity\PageVariant;
+use Drupal\page_manager\PageInterface;
+use Drupal\user\SharedTempStoreFactory;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Provides a form for adding a variant.
+ */
+class PageReorderVariantsForm extends FormBase {
+
+  /**
+   * Tempstore factory.
+   *
+   * @var \Drupal\user\SharedTempStoreFactory
+   */
+  protected $tempstore;
+
+  /**
+   * Constructs a new DisplayVariantAddForm.
+   *
+   * @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 'page_manager.page';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'page_manager_reorder_variants_form';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state, $machine_name = '') {
+    $cached_values = $this->tempstore->get($this->getTempstoreId())->get($machine_name);
+    $form_state->setTemporaryValue('wizard', $cached_values);
+    /** @var $page \Drupal\page_manager\Entity\Page */
+    $page = $cached_values['page'];
+
+    $form['variants'] = [
+      '#type' => 'table',
+      '#header' => [
+        $this->t('Label'),
+        $this->t('Plugin'),
+        $this->t('Weight'),
+      ],
+      '#empty' => $this->t('There are no variants.'),
+      '#tabledrag' => [[
+        'action' => 'order',
+        'relationship' => 'sibling',
+        'group' => 'variant-weight',
+      ]],
+    ];
+    /** @var \Drupal\page_manager\PageVariantInterface $page_variant */
+    foreach ($page->getVariants() as $page_variant) {
+      $row = [
+        '#attributes' => [
+          'class' => ['draggable'],
+        ],
+      ];
+      $row['label']['#markup'] = $page_variant->label();
+      $row['id']['#markup'] = $page_variant->getVariantPlugin()->adminLabel();
+      $row['weight'] = [
+        '#type' => 'weight',
+        '#default_value' => $page_variant->getWeight(),
+        '#title' => $this->t('Weight for @page_variant variant', ['@page_variant' => $page_variant->label()]),
+        '#title_display' => 'invisible',
+        '#attributes' => [
+          'class' => ['variant-weight'],
+        ],
+      ];
+      $form['variants'][$page_variant->id()] = $row;
+    }
+
+    $form['submit'] = [
+      '#type' => 'submit',
+      '#value' => $this->t('Update'),
+    ];
+
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    $cached_values = $form_state->getTemporaryValue('wizard');
+    /** @var \Drupal\page_manager\Entity\Page $page */
+    $page = $cached_values['page'];
+
+    foreach ($form_state->getValue('variants') as $id => $values) {
+      if ($page_variant = $page->getVariant($id)) {
+        $page_variant->setWeight($values['weight']);
+      }
+    }
+
+    $form_state->setRedirect('entity.page.edit_form', [
+      'machine_name' => $page->id(),
+      'step' => 'general',
+    ]);
+
+    $this->tempstore->get($this->getTempstoreId())->set($page->id(), $cached_values);
+  }
+
+}
diff --git a/src/Form/PageVariantAddForm.php b/src/Form/PageVariantAddForm.php
index 7f692ab..7a3d539 100644
--- a/src/Form/PageVariantAddForm.php
+++ b/src/Form/PageVariantAddForm.php
@@ -7,26 +7,156 @@
 
 namespace Drupal\page_manager\Form;
 
+use Drupal\Core\Display\VariantManager;
+use Drupal\Core\Form\FormBase;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\page_manager\Entity\PageVariant;
+use Drupal\page_manager\PageInterface;
+use Drupal\user\SharedTempStoreFactory;
+use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
  * Provides a form for adding a variant.
  */
-class PageVariantAddForm extends PageVariantFormBase {
+class PageVariantAddForm extends FormBase {
+
+  /**
+   * The variant manager.
+   *
+   * @var \Drupal\Core\Display\VariantManager
+   */
+  protected $variantManager;
+
+  /**
+   * Tempstore factory.
+   *
+   * @var \Drupal\user\SharedTempStoreFactory
+   */
+  protected $tempstore;
+
+  /**
+   * Constructs a new DisplayVariantAddForm.
+   *
+   * @param \Drupal\Core\Display\VariantManager $variant_manager
+   *   The variant manager.
+   */
+  public function __construct(VariantManager $variant_manager, SharedTempStoreFactory $tempstore) {
+    $this->variantManager = $variant_manager;
+    $this->tempstore = $tempstore;
+  }
 
   /**
    * {@inheritdoc}
    */
-  protected function submitText() {
-    return $this->t('Add variant');
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('plugin.manager.display_variant'),
+      $container->get('user.shared_tempstore')
+    );
+  }
+
+  /**
+   * Get the tempstore id.
+   *
+   * @return string
+   */
+  protected function getTempstoreId() {
+    return 'page_manager.page';
   }
 
   /**
    * {@inheritdoc}
    */
-  public function save(array $form, FormStateInterface $form_state) {
-    parent::save($form, $form_state);
-    $form_state->setRedirectUrl($this->getEntity()->toUrl('edit-form'));
+  public function getFormId() {
+    return 'page_manager_add_variant_form';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state, $machine_name = '') {
+    $cached_values = $this->tempstore->get($this->getTempstoreId())->get($machine_name);
+    $form_state->setTemporaryValue('wizard', $cached_values);
+    /** @var $page \Drupal\page_manager\Entity\Page */
+    $page = $cached_values['page'];
+
+    $variant_plugin_options = [];
+    foreach ($this->variantManager->getDefinitions() as $plugin_id => $definition) {
+      $variant_plugin_options[$plugin_id] = $definition['admin_label'];
+    }
+    $form['variant_plugin_id'] = [
+      '#title' => $this->t('Type'),
+      '#type' => 'select',
+      '#options' => $variant_plugin_options,
+      '#default_value' => !empty($cached_values['variant_plugin_id']) ? $cached_values['variant_plugin_id'] : '',
+    ];
+
+    $form['label'] = [
+      '#type' => 'textfield',
+      '#title' => $this->t('Label'),
+      '#required' => TRUE,
+      '#size' => 32,
+      '#maxlength' => 255,
+    ];
+    $form['id'] = [
+      '#type' => 'machine_name',
+      '#maxlength' => 128,
+      '#machine_name' => [
+        'source' => array('label'),
+        'exists' => function ($id) use ($page) {
+          return $this->variantExists($page, $id);
+        },
+      ],
+      '#description' => $this->t('A unique machine-readable name for this variant.'),
+    ];
+
+    $form['submit'] = [
+      '#type' => 'submit',
+      '#value' => $this->t('Create variant'),
+    ];
+
+    return $form;
+  }
+
+  /**
+   * Check if a variant id is taken.
+   *
+   * @param \Drupal\page_manager\PageInterface $page
+   *   The page entity.
+   * @param string $variant_id
+   *   The page variant id to check.
+   *
+   * @return bool
+   *   TRUE if the ID is available; FALSE otherwise.
+   */
+  protected function variantExists(PageInterface $page, $variant_id) {
+    return isset($page->getVariants()[$variant_id]) || PageVariant::load($variant_id);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    $cached_values = $form_state->getTemporaryValue('wizard');
+    /** @var \Drupal\page_manager\Entity\Page $page */
+    $page = $cached_values['page'];
+
+    $cached_values['page_variant'] = \Drupal::entityManager()
+      ->getStorage('page_variant')
+      ->create([
+        'variant' => $form_state->getValue('variant_plugin_id'),
+        'page' => $page->id(),
+        'id' => $form_state->getValue('id'),
+        'label' => $form_state->getValue('label'),
+      ]);
+    $page->addVariant($cached_values['page_variant']);
+
+    $form_state->setRedirect('entity.page.edit_form', [
+      'machine_name' => $page->id(),
+      'step' => 'page_variant__' . $cached_values['page_variant']->id() . '__overview',
+    ]);
+
+    $this->tempstore->get($this->getTempstoreId())->set($page->id(), $cached_values);
   }
 
 }
diff --git a/src/Form/PageVariantConfigureForm.php b/src/Form/PageVariantConfigureForm.php
new file mode 100644
index 0000000..758b9af
--- /dev/null
+++ b/src/Form/PageVariantConfigureForm.php
@@ -0,0 +1,94 @@
+<?php
+/**
+ * @file
+ * Contains \Drupal\page_manager\Form\PageVariantConfigureForm.
+ */
+
+namespace Drupal\page_manager\Form;
+
+
+use Drupal\Core\Form\FormBase;
+use Drupal\Core\Form\FormState;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Url;
+
+class PageVariantConfigureForm extends FormBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    // @todo this should vary by step/variant plugin id.
+    return 'page_manage_variant_configure_form';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state) {
+    $cached_values = $form_state->getTemporaryValue('wizard');
+    /** @var \Drupal\page_manager\PageInterface $page */
+    $page = $cached_values['page'];
+    /** @var \Drupal\page_manager\PageVariantInterface $page_variant */
+    $page_variant = $cached_values['page_variant'];
+
+    $form['page_variant_label'] = [
+      '#type' => 'textfield',
+      '#title' => $this->t('Label'),
+      '#required' => TRUE,
+      '#size' => 32,
+      '#maxlength' => 255,
+      '#default_value' => $page_variant->label(),
+    ];
+
+    $variant_plugin = $page_variant->getVariantPlugin();
+    $form['variant_settings'] = $variant_plugin->buildConfigurationForm([], (new FormState())->setValues($form_state->getValue('variant_settings', [])));
+    $form['variant_settings']['#tree'] = TRUE;
+
+    if (!$page->isNew()) {
+      $form['delete'] = [
+        '#type' => 'link',
+        '#title' => $this->t('Delete this variant'),
+        '#attributes' => [
+          'class' => ['button', 'use-ajax'],
+          'data-dialog-type' => 'modal',
+        ],
+        '#url' => new Url('entity.page_variant.delete_form', [
+          'machine_name' => $page->id(),
+          'variant_machine_name' => $page_variant->id(),
+        ]),
+      ];
+    }
+
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateForm(array &$form, FormStateInterface $form_state) {
+    $cached_values = $form_state->getTemporaryValue('wizard');
+    /** @var $page_variant \Drupal\page_manager\Entity\PageVariant */
+    $page_variant = $cached_values['page_variant'];
+
+    $variant_plugin = $page_variant->getVariantPlugin();
+    $variant_plugin->validateConfigurationForm($form['variant_settings'], (new FormState())->setValues($form_state->getValue('variant_settings', [])));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    $cached_values = $form_state->getTemporaryValue('wizard');
+    /** @var $page \Drupal\page_manager\Entity\Page */
+    $page = $cached_values['page'];
+    /** @var $page_variant \Drupal\page_manager\Entity\PageVariant */
+    $page_variant = $cached_values['page_variant'];
+    $variant_plugin = $page_variant->getVariantPlugin();
+    $variant_plugin->submitConfigurationForm($form['variant_settings'], (new FormState())->setValues($form_state->getValue('variant_settings', [])));
+    $configuration = $variant_plugin->getConfiguration();
+    $page_variant->set('variant_settings', $configuration);
+    $page_variant->set('label', $form_state->getValue('page_variant_label'));
+  }
+
+}
diff --git a/src/Form/PageVariantDeleteForm.php b/src/Form/PageVariantDeleteForm.php
index cafcd8d..761884f 100644
--- a/src/Form/PageVariantDeleteForm.php
+++ b/src/Form/PageVariantDeleteForm.php
@@ -7,35 +7,74 @@
 
 namespace Drupal\page_manager\Form;
 
-use Drupal\Core\Entity\EntityConfirmFormBase;
+use Drupal\Core\Form\ConfirmFormBase;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Url;
+use Drupal\user\SharedTempStoreFactory;
+use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
  * Builds the form to delete a PageVariant.
  */
-class PageVariantDeleteForm extends EntityConfirmFormBase {
+class PageVariantDeleteForm extends ConfirmFormBase {
 
   /**
-   * The entity being used by this form.
+   * Tempstore factory.
    *
-   * @var \Drupal\page_manager\PageVariantInterface
+   * @var \Drupal\user\SharedTempStoreFactory
    */
-  protected $entity;
+  protected $tempstore;
+
+  /**
+   * Constructs a PageVariantDeleteForm.
+   *
+   * @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 'page_manager.page';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'page_manager_variant_delete_form';
+  }
 
   /**
    * {@inheritdoc}
    */
   public function getQuestion() {
-    return $this->t('Are you sure you want to delete %name?', ['%name' => $this->entity->label()]);
+    return $this->t('Are you sure you want to delete this variant?');
   }
 
   /**
    * {@inheritdoc}
    */
   public function getCancelUrl() {
+    $machine_name = $this->getRouteMatch()->getParameter('machine_name');
     return new Url('entity.page.edit_form', [
-      'page' => $this->entity->get('page'),
+      'machine_name' => $machine_name,
+      'step' => 'general',
     ]);
   }
 
@@ -50,13 +89,23 @@ class PageVariantDeleteForm extends EntityConfirmFormBase {
    * {@inheritdoc}
    */
   public function submitForm(array &$form, FormStateInterface $form_state) {
-    $this->entity->delete();
+    $machine_name = $this->getRouteMatch()->getParameter('machine_name');
+    $variant_machine_name = $this->getRouteMatch()->getParameter('variant_machine_name');
+    $cached_values = $this->tempstore->get($this->getTempstoreId())->get($machine_name);
+    /** @var \Drupal\page_manager\PageInterface $page */
+    $page = $cached_values['page'];
+    $page_variant = $page->getVariant($variant_machine_name);
+
+    // Add to a list to remove for real later.
+    $cached_values['deleted_variants'][] = $page_variant;
 
     drupal_set_message($this->t('The variant %label has been removed.', [
-      '%label' => $this->entity->label(),
+      '%label' => $page_variant->label(),
     ]));
 
     $form_state->setRedirectUrl($this->getCancelUrl());
+
+    $this->tempstore->get($this->getTempstoreId())->set($page->id(), $cached_values);
   }
 
 }
diff --git a/src/Form/PageVariantEditForm.php b/src/Form/PageVariantEditForm.php
deleted file mode 100644
index 4a25382..0000000
--- a/src/Form/PageVariantEditForm.php
+++ /dev/null
@@ -1,313 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains Drupal\page_manager\Form\PageVariantEditForm.
- */
-
-namespace Drupal\page_manager\Form;
-
-use Drupal\Core\Form\FormStateInterface;
-use Drupal\Core\Url;
-use Drupal\ctools\Form\AjaxFormTrait;
-use Drupal\ctools\Plugin\BlockVariantInterface;
-
-/**
- * Provides a form for editing a variant.
- */
-class PageVariantEditForm extends PageVariantFormBase {
-
-  use AjaxFormTrait;
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function submitText() {
-    return $this->t('Update variant');
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function form(array $form, FormStateInterface $form_state) {
-    $form = parent::form($form, $form_state);
-
-    if ($this->getVariantPlugin() instanceof BlockVariantInterface) {
-      $form['variant_settings']['block_section'] = $this->buildBlockForm();
-    }
-
-    $form['selection_section'] = $this->buildSelectionForm();
-
-    return $form;
-  }
-
-  /**
-   * Builds the block form for a variant.
-   *
-   * @return array
-   */
-  protected function buildBlockForm() {
-    $variant_plugin = $this->getVariantPlugin();
-    if (!$variant_plugin instanceof BlockVariantInterface) {
-      return [];
-    }
-
-    /** @var \Drupal\page_manager\PageVariantInterface $page_variant */
-    $page_variant = $this->getEntity();
-
-    // Set up the attributes used by a modal to prevent duplication later.
-    $attributes = $this->getAjaxAttributes();
-    $add_button_attributes = $this->getAjaxButtonAttributes();
-
-    $form = [];
-    if ($block_assignments = $variant_plugin->getRegionAssignments()) {
-      // Build a table of all blocks used by this variant.
-      $form = [
-        '#type' => 'details',
-        '#title' => $this->t('Blocks'),
-        '#open' => TRUE,
-      ];
-      $form['add'] = [
-        '#type' => 'link',
-        '#title' => $this->t('Add new block'),
-        '#url' => Url::fromRoute('page_manager.variant_select_block', [
-          'page' => $page_variant->get('page'),
-          'page_variant' => $page_variant->id(),
-        ]),
-        '#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.'),
-        // @todo This should utilize https://drupal.org/node/2065485.
-        '#parents' => ['variant_plugin', '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('page_manager.variant_edit_block', [
-              'page' => $page_variant->get('page'),
-              'page_variant' => $page_variant->id(),
-              'block_id' => $block_id,
-            ]),
-            'attributes' => $attributes,
-          ];
-          $operations['delete'] = [
-            'title' => $this->t('Delete'),
-            'url' => Url::fromRoute('page_manager.variant_delete_block', [
-              'page' => $page_variant->get('page'),
-              'page_variant' => $page_variant->id(),
-              'block_id' => $block_id,
-            ]),
-            'attributes' => $attributes,
-          ];
-
-          $row['operations'] = [
-            '#type' => 'operations',
-            '#links' => $operations,
-          ];
-          $form['blocks'][$block_id] = $row;
-        }
-      }
-    }
-    return $form;
-  }
-
-   /**
-   * Builds the selection form for a variant.
-   *
-   * @return array
-   */
-  protected function buildSelectionForm() {
-    // Set up the attributes used by a modal to prevent duplication later.
-    $attributes = $this->getAjaxAttributes();
-    $add_button_attributes = $this->getAjaxButtonAttributes();
-
-    /** @var \Drupal\page_manager\PageVariantInterface $page_variant */
-    $page_variant = $this->getEntity();
-
-    // Selection conditions.
-    $form = [
-      '#type' => 'details',
-      '#title' => $this->t('Selection Conditions'),
-      '#open' => TRUE,
-    ];
-    $form['add'] = [
-      '#type' => 'link',
-      '#title' => $this->t('Add new selection condition'),
-      '#url' => Url::fromRoute('page_manager.selection_condition_select', [
-        'page' => $page_variant->get('page'),
-        'page_variant' => $page_variant->id(),
-      ]),
-      '#attributes' => $add_button_attributes,
-      '#attached' => [
-        'library' => [
-          'core/drupal.ajax',
-        ],
-      ],
-    ];
-    $form['table'] = [
-      '#type' => 'table',
-      '#header' => [
-        $this->t('Label'),
-        $this->t('Description'),
-        $this->t('Operations'),
-      ],
-      '#empty' => $this->t('There are no selection conditions.'),
-    ];
-
-    $form['selection_logic'] = [
-      '#type' => 'radios',
-      '#options' => [
-        'and' => $this->t('All conditions must pass'),
-        'or' => $this->t('Only one condition must pass'),
-      ],
-      '#default_value' => $page_variant->getSelectionLogic(),
-    ];
-
-    $form['selection'] = [
-      '#tree' => TRUE,
-    ];
-    foreach ($page_variant->getSelectionConditions() as $selection_id => $selection_condition) {
-      $row = [];
-      $row['label']['#markup'] = $selection_condition->getPluginDefinition()['label'];
-      $row['description']['#markup'] = $selection_condition->summary();
-      $operations = [];
-      $operations['edit'] = [
-        'title' => $this->t('Edit'),
-        'url' => Url::fromRoute('page_manager.selection_condition_edit', [
-          'page' => $page_variant->get('page'),
-          'page_variant' => $page_variant->id(),
-          'condition_id' => $selection_id,
-        ]),
-        'attributes' => $attributes,
-      ];
-      $operations['delete'] = [
-        'title' => $this->t('Delete'),
-        'url' => Url::fromRoute('page_manager.selection_condition_delete', [
-          'page' => $page_variant->get('page'),
-          'page_variant' => $page_variant->id(),
-          'condition_id' => $selection_id,
-        ]),
-        'attributes' => $attributes,
-      ];
-      $row['operations'] = [
-        '#type' => 'operations',
-        '#links' => $operations,
-      ];
-      $form['table'][$selection_id] = $row;
-    }
-
-    return $form;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function save(array $form, FormStateInterface $form_state) {
-    // @todo This feels very wrong.
-    $variant_plugin = $this->getVariantPlugin();
-    if ($variant_plugin instanceof BlockVariantInterface) {
-      // If the blocks were rearranged, update their values.
-      if (!$form_state->isValueEmpty(['variant_plugin', 'blocks'])) {
-        foreach ($form_state->getValue(['variant_plugin', 'blocks']) as $block_id => $block_values) {
-          $variant_plugin->updateBlock($block_id, $block_values);
-        }
-      }
-    }
-
-    parent::save($form, $form_state);
-
-    $form_state->setRedirect('entity.page.edit_form', ['page' => $this->entity->get('page')]);
-  }
-
-}
diff --git a/src/Form/PageVariantFormBase.php b/src/Form/PageVariantFormBase.php
deleted file mode 100644
index 09961a8..0000000
--- a/src/Form/PageVariantFormBase.php
+++ /dev/null
@@ -1,177 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains Drupal\page_manager\Form\PageVariantFormBase.
- */
-
-namespace Drupal\page_manager\Form;
-
-use Drupal\Core\Entity\EntityForm;
-use Drupal\Core\Entity\Query\QueryFactory;
-use Drupal\Core\Form\FormState;
-use Drupal\Core\Form\FormStateInterface;
-use Symfony\Component\DependencyInjection\ContainerInterface;
-
-/**
- * Provides a base form for editing and adding a page variant.
- */
-abstract class PageVariantFormBase extends EntityForm {
-
-  /**
-   * The entity being used by this form.
-   *
-   * @var \Drupal\page_manager\PageVariantInterface
-   */
-  protected $entity;
-
-  /**
-   * The variant plugin for this page variant entity.
-   *
-   * @var \Drupal\Core\Display\VariantInterface
-   */
-  protected $variantPlugin;
-
-  /**
-   * The entity query factory.
-   *
-   * @var \Drupal\Core\Entity\Query\QueryFactory
-   */
-  protected $entityQuery;
-
-  /**
-   * Construct a new PageFormBase.
-   *
-   * @param \Drupal\Core\Entity\Query\QueryFactory $entity_query
-   *   The entity query factory.
-   */
-  public function __construct(QueryFactory $entity_query) {
-    $this->entityQuery = $entity_query;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function create(ContainerInterface $container) {
-    return new static(
-      $container->get('entity.query')
-    );
-  }
-
-  /**
-   * Returns the text to use for the submit button.
-   *
-   * @return string
-   *   The submit button text.
-   */
-  abstract protected function submitText();
-
-  /**
-   * {@inheritdoc}
-   */
-  public function form(array $form, FormStateInterface $form_state) {
-    $form = parent::form($form, $form_state);
-
-    $form['label'] = [
-      '#type' => 'textfield',
-      '#title' => $this->t('Label'),
-      '#description' => $this->t('The label for this variant.'),
-      '#default_value' => $this->entity->label() ?: (string) $this->getVariantPlugin()->adminLabel(),
-      '#maxlength' => '255',
-    ];
-
-    $form['id'] = [
-      '#type' => 'machine_name',
-      '#disabled' => !$this->entity->isNew(),
-      '#default_value' => !$this->entity->isNew() ? $this->entity->id() : '',
-      '#machine_name' => [
-        'exists' => [$this, 'exists'],
-      ],
-    ];
-
-    // Allow the variant to add to the form.
-    $form['variant_settings'] = $this->getVariantPlugin()->buildConfigurationForm([], $form_state);
-    $form['variant_settings']['#tree'] = TRUE;
-
-    $form['actions'] = ['#type' => 'actions'];
-    $form['actions']['submit'] = [
-      '#type' => 'submit',
-      '#value' => 'Add/Edit',
-      '#button_type' => 'primary',
-    ];
-
-    return $form;
-  }
-
-  /**
-   * Determines if the page variant entity already exists.
-   *
-   * @param string $id
-   *   The page variant entity ID.
-   *
-   * @return bool
-   *   TRUE if the entity exists, FALSE otherwise.
-   */
-  public function exists($id) {
-    return (bool) $this->entityQuery->get('page_variant')
-      ->condition('id', $id)
-      ->execute();
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function save(array $form, FormStateInterface $form_state) {
-    $status = parent::save($form, $form_state);
-
-    if ($status) {
-      drupal_set_message($this->t('Saved the %label variant.', [
-        '%label' => $this->entity->label(),
-      ]));
-    }
-    else {
-      drupal_set_message($this->t('The %label variant was not saved.', [
-        '%label' => $this->entity->label(),
-      ]));
-    }
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function validateForm(array &$form, FormStateInterface $form_state) {
-    parent::validateForm($form, $form_state);
-
-    // Allow the variant to validate the form.
-    $variant_plugin_values = (new FormState())->setValues($form_state->getValue('variant_settings'));
-    $this->getVariantPlugin()->validateConfigurationForm($form, $variant_plugin_values);
-    // Update the original form values.
-    $form_state->setValue('variant_settings', $variant_plugin_values->getValues());
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function submitForm(array &$form, FormStateInterface $form_state) {
-    // Allow the variant to submit the form.
-    $variant_plugin_values = (new FormState())->setValues($form_state->getValue('variant_settings'));
-    $this->getVariantPlugin()->submitConfigurationForm($form, $variant_plugin_values);
-    // Update the original form values.
-    $form_state->setValue('variant_settings', $variant_plugin_values->getValues());
-
-    parent::submitForm($form, $form_state);
-  }
-
-  /**
-   * Gets the variant plugin for this page variant entity.
-   *
-   * @return \Drupal\Core\Display\VariantInterface
-   */
-  protected function getVariantPlugin() {
-    if (!$this->variantPlugin) {
-      $this->variantPlugin = $this->entity->getVariantPlugin();
-    }
-    return $this->variantPlugin;
-  }
-
-}
diff --git a/src/Form/PageVariantSelectionForm.php b/src/Form/PageVariantSelectionForm.php
new file mode 100644
index 0000000..930096b
--- /dev/null
+++ b/src/Form/PageVariantSelectionForm.php
@@ -0,0 +1,73 @@
+<?php
+/**
+ * @file
+ * Contains \Drupal\page_manager\Form\PageAccessForm.
+ */
+
+namespace Drupal\page_manager\Form;
+
+
+use Drupal\ctools\Form\ManageConditions;
+
+class PageVariantSelectionForm extends ManageConditions {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'page_manager_access_form';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getConditionClass() {
+    return 'Drupal\page_manager\Form\SelectionConfigure';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getTempstoreId() {
+    return 'page_manager.page';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getOperationsRouteInfo($cached_values, $machine_name, $row) {
+    /** @var \Drupal\page_manager\PageVariantInterface $page_variant */
+    $page_variant = $cached_values['page_variant'];
+    return ['entity.page_variant.condition', [
+      'machine_name' => $machine_name,
+      'variant_machine_name' => $page_variant->id(),
+      'condition' => $row
+    ]];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getConditions($cached_values) {
+    /** @var $page \Drupal\page_manager\Entity\PageVariant */
+    $page_variant = $cached_values['page_variant'];
+    return $page_variant->get('selection_criteria');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getContexts($cached_values) {
+    /** @var $page \Drupal\page_manager\Entity\Page */
+    $page = $cached_values['page'];
+    return $page->getContexts();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getAddRoute($cached_values) {
+    return 'entity.page_variant.condition.add';
+  }
+
+}
diff --git a/src/Form/SelectionConditionAddForm.php b/src/Form/SelectionConditionAddForm.php
deleted file mode 100644
index f30d423..0000000
--- a/src/Form/SelectionConditionAddForm.php
+++ /dev/null
@@ -1,73 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\page_manager\Form\SelectionConditionAddForm.
- */
-
-namespace Drupal\page_manager\Form;
-
-use Drupal\Core\Condition\ConditionManager;
-use Symfony\Component\DependencyInjection\ContainerInterface;
-
-/**
- * Provides a form for adding a new selection condition.
- */
-class SelectionConditionAddForm extends SelectionConditionFormBase {
-
-  /**
-   * The condition manager.
-   *
-   * @var \Drupal\Core\Condition\ConditionManager
-   */
-  protected $conditionManager;
-
-  /**
-   * Constructs a new SelectionConditionAddForm.
-   *
-   * @param \Drupal\Core\Condition\ConditionManager $condition_manager
-   *   The condition manager.
-   */
-  public function __construct(ConditionManager $condition_manager) {
-    $this->conditionManager = $condition_manager;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function create(ContainerInterface $container) {
-    return new static(
-      $container->get('plugin.manager.condition')
-    );
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getFormId() {
-    return 'page_manager_selection_condition_add_form';
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function prepareCondition($condition_id) {
-    // Create a new selection condition instance.
-    return $this->conditionManager->createInstance($condition_id);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function submitButtonText() {
-    return $this->t('Add selection condition');
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function submitMessageText() {
-    return $this->t('The %label selection condition has been added.', ['%label' => $this->condition->getPluginDefinition()['label']]);
-  }
-
-}
diff --git a/src/Form/SelectionConditionDeleteForm.php b/src/Form/SelectionConditionDeleteForm.php
deleted file mode 100644
index 15ad9dd..0000000
--- a/src/Form/SelectionConditionDeleteForm.php
+++ /dev/null
@@ -1,81 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\page_manager\Form\SelectionConditionDeleteForm.
- */
-
-namespace Drupal\page_manager\Form;
-
-use Drupal\Core\Form\FormStateInterface;
-use Drupal\Core\Form\ConfirmFormBase;
-use Drupal\Core\Url;
-use Drupal\page_manager\PageVariantInterface;
-
-/**
- * Provides a form for deleting a selection condition.
- */
-class SelectionConditionDeleteForm extends ConfirmFormBase {
-
-  /**
-   * The page entity this selection condition belongs to.
-   *
-   * @var \Drupal\page_manager\PageVariantInterface
-   */
-  protected $pageVariant;
-
-  /**
-   * The selection condition used by this form.
-   *
-   * @var \Drupal\Core\Condition\ConditionInterface
-   */
-  protected $selectionCondition;
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getFormId() {
-    return 'page_manager_selection_condition_delete_form';
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getQuestion() {
-    return $this->t('Are you sure you want to delete the selection condition %name?', ['%name' => $this->selectionCondition->getPluginDefinition()['label']]);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getCancelUrl() {
-    return $this->pageVariant->toUrl('edit-form');
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getConfirmText() {
-    return $this->t('Delete');
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function buildForm(array $form, FormStateInterface $form_state, PageVariantInterface $page_variant = NULL, $condition_id = NULL) {
-    $this->pageVariant = $page_variant;
-    $this->selectionCondition = $page_variant->getSelectionCondition($condition_id);
-    return parent::buildForm($form, $form_state);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function submitForm(array &$form, FormStateInterface $form_state) {
-    $this->pageVariant->removeSelectionCondition($this->selectionCondition->getConfiguration()['uuid']);
-    $this->pageVariant->save();
-    drupal_set_message($this->t('The selection condition %name has been removed.', ['%name' => $this->selectionCondition->getPluginDefinition()['label']]));
-    $form_state->setRedirectUrl($this->getCancelUrl());
-  }
-
-}
diff --git a/src/Form/SelectionConditionEditForm.php b/src/Form/SelectionConditionEditForm.php
deleted file mode 100644
index 91c0ee2..0000000
--- a/src/Form/SelectionConditionEditForm.php
+++ /dev/null
@@ -1,44 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\page_manager\Form\SelectionConditionEditForm.
- */
-
-namespace Drupal\page_manager\Form;
-
-/**
- * Provides a form for editing an selection condition.
- */
-class SelectionConditionEditForm extends SelectionConditionFormBase {
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getFormId() {
-    return 'page_manager_selection_condition_edit_form';
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function prepareCondition($condition_id) {
-    // Load the selection condition directly from the variant.
-    return $this->pageVariant->getSelectionCondition($condition_id);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function submitButtonText() {
-    return $this->t('Update selection condition');
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function submitMessageText() {
-    return $this->t('The %label selection condition has been updated.', ['%label' => $this->condition->getPluginDefinition()['label']]);
-  }
-
-}
diff --git a/src/Form/SelectionConditionFormBase.php b/src/Form/SelectionConditionFormBase.php
deleted file mode 100644
index 8c44df0..0000000
--- a/src/Form/SelectionConditionFormBase.php
+++ /dev/null
@@ -1,51 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\page_manager\Form\SelectionConditionFormBase.
- */
-
-namespace Drupal\page_manager\Form;
-
-use Drupal\Core\Form\FormStateInterface;
-use Drupal\page_manager\PageVariantInterface;
-
-/**
- * Provides a base form for editing and adding a selection condition.
- */
-abstract class SelectionConditionFormBase extends ConditionFormBase {
-
-  /**
-   * The page variant entity.
-   *
-   * @var \Drupal\page_manager\PageVariantInterface
-   */
-  protected $pageVariant;
-
-  /**
-   * {@inheritdoc}
-   */
-  public function buildForm(array $form, FormStateInterface $form_state, PageVariantInterface $page_variant = NULL, $condition_id = NULL) {
-    $this->pageVariant = $page_variant;
-    return parent::buildForm($form, $form_state, $condition_id, $page_variant->getContexts());
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function submitForm(array &$form, FormStateInterface $form_state) {
-    parent::submitForm($form, $form_state);
-
-    $configuration = $this->condition->getConfiguration();
-    // If this selection condition is new, add it to the page.
-    if (!isset($configuration['uuid'])) {
-      $this->pageVariant->addSelectionCondition($configuration);
-    }
-
-    // Save the page entity.
-    $this->pageVariant->save();
-
-    $form_state->setRedirectUrl($this->pageVariant->toUrl('edit-form'));
-  }
-
-}
diff --git a/src/Form/SelectionConfigure.php b/src/Form/SelectionConfigure.php
new file mode 100644
index 0000000..b92b00e
--- /dev/null
+++ b/src/Form/SelectionConfigure.php
@@ -0,0 +1,96 @@
+<?php
+/**
+ * @file
+ * Contains \Drupal\page_manager\Form\SelectionConfigure.
+ */
+
+namespace Drupal\page_manager\Form;
+
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\ctools\Form\ConditionConfigure;
+
+class SelectionConfigure extends ConditionConfigure {
+
+  /**
+   * The machine-name of the variant.
+   *
+   * @var string
+   */
+  protected $variantMachineName;
+
+  /**
+   * Get the page variant.
+   *
+   * @param array $cached_values
+   *   The cached values from the wizard.
+   *
+   * @return \Drupal\page_manager\PageVariantInterface
+   */
+  protected function getPageVariant($cached_values) {
+    if (isset($cached_values['page_variant'])) {
+      return $cached_values['page_variant'];
+    }
+
+    /** @var $page \Drupal\page_manager\PageInterface */
+    $page = $cached_values['page'];
+    return $page->getVariant($this->variantMachineName);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getParentRouteInfo($cached_values) {
+    /** @var $page \Drupal\page_manager\PageInterface */
+    $page = $cached_values['page'];
+
+    if ($page->isNew()) {
+      return ['entity.page.add_step_form', [
+        'machine_name' => $this->machine_name,
+        'step' => 'selection',
+      ]];
+    }
+    else {
+      $page_variant = $this->getPageVariant($cached_values);
+      return ['entity.page.edit_form', [
+        'machine_name' => $this->machine_name,
+        'step' => 'page_variant__' . $page_variant->id() . '__selection',
+      ]];
+    }
+  }
+
+  /**
+   * @inheritDoc
+   */
+  public function buildForm(array $form, FormStateInterface $form_state, $condition = NULL, $tempstore_id = NULL, $machine_name = NULL, $variant_machine_name = NULL) {
+    $this->variantMachineName = $variant_machine_name;
+    return parent::buildForm($form, $form_state, $condition, $tempstore_id, $machine_name);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getConditions($cached_values) {
+    $page_variant = $this->getPageVariant($cached_values);
+    return $page_variant->get('selection_criteria');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setConditions($cached_values, $conditions) {
+    $page_variant = $this->getPageVariant($cached_values);
+    $page_variant->set('selection_criteria', $conditions);
+    return $cached_values;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getContexts($cached_values) {
+    /** @var $page \Drupal\page_manager\Entity\Page */
+    //$page = $cached_values['page'];
+    //return $page->getContexts();
+    return [];
+  }
+
+}
diff --git a/src/Form/SelectionDelete.php b/src/Form/SelectionDelete.php
new file mode 100644
index 0000000..946b7ad
--- /dev/null
+++ b/src/Form/SelectionDelete.php
@@ -0,0 +1,105 @@
+<?php
+/**
+ * @file
+ * Contains \Drupal\page_manager\Form\SelectionDelete.
+ */
+
+namespace Drupal\page_manager\Form;
+
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\ctools\Form\ConditionDelete;
+
+class SelectionDelete extends ConditionDelete {
+
+  /**
+   * The machine-name of the variant.
+   *
+   * @var string
+   */
+  protected $variantMachineName;
+
+  /**
+   * Get the page variant.
+   *
+   * @param array $cached_values
+   *   The cached values from the wizard.
+   *
+   * @return \Drupal\page_manager\PageVariantInterface
+   */
+  protected function getPageVariant($cached_values) {
+    if (isset($cached_values['page_variant'])) {
+      return $cached_values['page_variant'];
+    }
+
+    /** @var $page \Drupal\page_manager\PageInterface */
+    $page = $cached_values['page'];
+    return $page->getVariant($this->variantMachineName);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getParentRouteInfo($cached_values) {
+    /** @var $page \Drupal\page_manager\PageInterface */
+    $page = $cached_values['page'];
+
+    if ($page->isNew()) {
+      return ['entity.page.add_step_form', [
+        'machine_name' => $this->machine_name,
+        'step' => 'selection',
+      ]];
+    }
+    else {
+      $page_variant = $this->getPageVariant($cached_values);
+      return ['entity.page.edit_form', [
+        'machine_name' => $this->machine_name,
+        'step' => 'page_variant__' . $page_variant->id() . '__selection',
+      ]];
+    }
+  }
+
+  /**
+   * ConditionDelete puts this function on #validate but it doesn't exist.
+   *
+   * @todo: Remove when #2640392 is fixed.
+   */
+  public function validate($form, FormStateInterface $form_state) {
+    // Do nothing.
+  }
+
+  /**
+   * @inheritDoc
+   */
+  public function buildForm(array $form, FormStateInterface $form_state, $id = NULL, $tempstore_id = NULL, $machine_name = NULL, $variant_machine_name = NULL) {
+    $this->variantMachineName = $variant_machine_name;
+    return parent::buildForm($form, $form_state, $id, $tempstore_id, $machine_name);
+  }
+
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getConditions($cached_values) {
+    $page_variant = $this->getPageVariant($cached_values);
+    return $page_variant->get('selection_criteria');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setConditions($cached_values, $conditions) {
+    $page_variant = $this->getPageVariant($cached_values);
+    $page_variant->set('selection_criteria', $conditions);
+    return $cached_values;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getContexts($cached_values) {
+    /** @var $page \Drupal\page_manager\Entity\Page */
+    $page = $cached_values['page'];
+    return $page->getContexts();
+  }
+
+}
diff --git a/src/Form/VariantPluginAddBlockForm.php b/src/Form/VariantPluginAddBlockForm.php
index 8b78cd8..38c9b00 100644
--- a/src/Form/VariantPluginAddBlockForm.php
+++ b/src/Form/VariantPluginAddBlockForm.php
@@ -10,6 +10,7 @@ namespace Drupal\page_manager\Form;
 use Drupal\Component\Plugin\PluginManagerInterface;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\page_manager\PageVariantInterface;
+use Drupal\user\SharedTempStoreFactory;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\HttpFoundation\Request;
 
@@ -31,7 +32,8 @@ class VariantPluginAddBlockForm extends VariantPluginConfigureBlockFormBase {
    * @param \Drupal\Component\Plugin\PluginManagerInterface $block_manager
    *   The block manager.
    */
-  public function __construct(PluginManagerInterface $block_manager) {
+  public function __construct(SharedTempStoreFactory $tempstore, PluginManagerInterface $block_manager) {
+    parent::__construct($tempstore);
     $this->blockManager = $block_manager;
   }
 
@@ -40,6 +42,7 @@ class VariantPluginAddBlockForm extends VariantPluginConfigureBlockFormBase {
    */
   public static function create(ContainerInterface $container) {
     return new static(
+      $container->get('user.shared_tempstore'),
       $container->get('plugin.manager.block')
     );
   }
@@ -63,8 +66,8 @@ class VariantPluginAddBlockForm extends VariantPluginConfigureBlockFormBase {
   /**
    * {@inheritdoc}
    */
-  public function buildForm(array $form, FormStateInterface $form_state, Request $request = NULL, PageVariantInterface $page_variant = NULL, $block_id = NULL) {
-    $form = parent::buildForm($form, $form_state, $page_variant, $block_id);
+  public function buildForm(array $form, FormStateInterface $form_state, Request $request = NULL, $block_display = NULL, $block_id = NULL) {
+    $form = parent::buildForm($form, $form_state, $block_display, $block_id);
     $form['region']['#default_value'] = $request->query->get('region');
     return $form;
   }
diff --git a/src/Form/VariantPluginConfigureBlockFormBase.php b/src/Form/VariantPluginConfigureBlockFormBase.php
index 1a729c4..48bdd73 100644
--- a/src/Form/VariantPluginConfigureBlockFormBase.php
+++ b/src/Form/VariantPluginConfigureBlockFormBase.php
@@ -13,6 +13,8 @@ use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Plugin\ContextAwarePluginAssignmentTrait;
 use Drupal\Core\Plugin\ContextAwarePluginInterface;
 use Drupal\page_manager\PageVariantInterface;
+use Drupal\user\SharedTempStoreFactory;
+use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
  * Provides a base form for configuring a block as part of a variant.
@@ -22,11 +24,18 @@ abstract class VariantPluginConfigureBlockFormBase extends FormBase {
   use ContextAwarePluginAssignmentTrait;
 
   /**
-   * The page entity.
+   * Tempstore factory.
    *
-   * @var \Drupal\page_manager\PageVariantInterface
+   * @var \Drupal\user\SharedTempStoreFactory
    */
-  protected $pageVariant;
+  protected $tempstore;
+
+  /**
+   * The variant plugin.
+   *
+   * @var \Drupal\page_manager\Plugin\DisplayVariant\PageBlockDisplayVariant
+   */
+  protected $variantPlugin;
 
   /**
    * The plugin being configured.
@@ -36,6 +45,43 @@ abstract class VariantPluginConfigureBlockFormBase extends FormBase {
   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 'page_manager.block_display';
+  }
+
+  /**
+   * 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
@@ -57,10 +103,10 @@ abstract class VariantPluginConfigureBlockFormBase extends FormBase {
   /**
    * {@inheritdoc}
    */
-  public function buildForm(array $form, FormStateInterface $form_state, PageVariantInterface $page_variant = NULL, $block_id = NULL) {
-    $this->pageVariant = $page_variant;
+  public function buildForm(array $form, FormStateInterface $form_state, $block_display = NULL, $block_id = NULL) {
+    $this->variantPlugin = $this->getTempstore()->get($block_display)['plugin'];
     $this->block = $this->prepareBlock($block_id);
-    $form_state->set('page_variant_id', $page_variant->id());
+    $form_state->set('variant_id', $this->getVariantPlugin()->id());
     $form_state->set('block_id', $this->block->getConfiguration()['uuid']);
 
     $form['#tree'] = TRUE;
@@ -78,7 +124,7 @@ abstract class VariantPluginConfigureBlockFormBase extends FormBase {
     ];
 
     if ($this->block instanceof ContextAwarePluginInterface) {
-      $form['context_mapping'] = $this->addContextAssignmentElement($this->block, $this->pageVariant->getContexts());
+      $form['context_mapping'] = $this->addContextAssignmentElement($this->block, $this->getVariantPlugin()->getContexts());
     }
 
     $form['actions']['submit'] = [
@@ -120,9 +166,10 @@ abstract class VariantPluginConfigureBlockFormBase extends FormBase {
     }
 
     $this->getVariantPlugin()->updateBlock($this->block->getConfiguration()['uuid'], ['region' => $form_state->getValue('region')]);
-    $this->pageVariant->save();
 
-    $form_state->setRedirectUrl($this->pageVariant->toUrl('edit-form'));
+    $cached_values = $this->getTempstore()->get($form_state->get('variant_id'));
+    $cached_values['plugin'] = $this->getVariantPlugin();
+    $this->getTempstore()->set($form_state->get('variant_id'), $cached_values);
   }
 
   /**
@@ -131,7 +178,7 @@ abstract class VariantPluginConfigureBlockFormBase extends FormBase {
    * @return \Drupal\ctools\Plugin\BlockVariantInterface
    */
   protected function getVariantPlugin() {
-    return $this->pageVariant->getVariantPlugin();
+    return $this->variantPlugin;
   }
 
 }
diff --git a/src/Form/VariantPluginContentForm.php b/src/Form/VariantPluginContentForm.php
new file mode 100644
index 0000000..8e8b8c6
--- /dev/null
+++ b/src/Form/VariantPluginContentForm.php
@@ -0,0 +1,251 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\page_manager\Form\VariantPluginContentForm.
+ */
+
+namespace Drupal\page_manager\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.
+ */
+class VariantPluginContentForm 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 'page_manager.block_display';
+  }
+
+  /**
+   * Get the tempstore.
+   *
+   * @return \Drupal\user\SharedTempStore
+   */
+  protected function getTempstore() {
+    return $this->tempstore->get($this->getTempstoreId());
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'page_manager_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'];
+
+    // Store the block display plugin so we can get it in our dialogs.
+    if (!empty($this->getTempstore()->get($variant_plugin->id()))) {
+      $cached_values['plugin'] = $variant_plugin = $this->getTempstore()->get($variant_plugin->id())['plugin'];
+      $form_state->setTemporaryValue('wizard', $cached_values);
+    }
+    else {
+      $this->getTempstore()->set($variant_plugin->id(), ['plugin' => $variant_plugin, 'access' => $cached_values['access']]);
+    }
+
+    // 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('page_manager.block_display_select_block', [
+          'block_display' => $variant_plugin->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('page_manager.block_display_edit_block', [
+              'block_display' => $variant_plugin->id(),
+              'block_id' => $block_id,
+              'destination' => $this->getRequest()->getRequestUri(),
+            ]),
+            'attributes' => $attributes,
+          ];
+          $operations['delete'] = [
+            'title' => $this->t('Delete'),
+            'url' => Url::fromRoute('page_manager.block_display_delete_block', [
+              'block_display' => $variant_plugin->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/VariantPluginDeleteBlockForm.php b/src/Form/VariantPluginDeleteBlockForm.php
index 37c8830..6ccbaa1 100644
--- a/src/Form/VariantPluginDeleteBlockForm.php
+++ b/src/Form/VariantPluginDeleteBlockForm.php
@@ -18,11 +18,9 @@ use Drupal\page_manager\PageVariantInterface;
 class VariantPluginDeleteBlockForm extends ConfirmFormBase {
 
   /**
-   * The page variant.
-   *
-   * @var \Drupal\page_manager\PageVariantInterface
+   * @var \Drupal\ctools\Plugin\BlockVariantInterface
    */
-  protected $pageVariant;
+  protected $plugin;
 
   /**
    * The plugin being configured.
@@ -32,6 +30,24 @@ class VariantPluginDeleteBlockForm extends ConfirmFormBase {
   protected $block;
 
   /**
+   * Get the tempstore id.
+   *
+   * @return string
+   */
+  protected function getTempstoreId() {
+    return 'page_manager.block_display';
+  }
+
+  /**
+   * Get the tempstore.
+   *
+   * @return \Drupal\user\SharedTempStore
+   */
+  protected function getTempstore() {
+    return \Drupal::service('user.shared_tempstore')->get($this->getTempstoreId());
+  }
+
+  /**
    * {@inheritdoc}
    */
   public function getFormId() {
@@ -49,7 +65,7 @@ class VariantPluginDeleteBlockForm extends ConfirmFormBase {
    * {@inheritdoc}
    */
   public function getCancelUrl() {
-    return $this->pageVariant->toUrl('edit-form');
+    return \Drupal::request()->attributes->get('destination');
   }
 
   /**
@@ -62,9 +78,13 @@ class VariantPluginDeleteBlockForm extends ConfirmFormBase {
   /**
    * {@inheritdoc}
    */
-  public function buildForm(array $form, FormStateInterface $form_state, PageVariantInterface $page_variant = NULL, $block_id = NULL) {
-    $this->pageVariant = $page_variant;
-    $this->block = $this->getVariantPlugin()->getBlock($block_id);
+  public function buildForm(array $form, FormStateInterface $form_state, $block_display = NULL, $block_id = NULL) {
+    $this->plugin = $this->getTempstore()->get($block_display)['plugin'];
+    $this->block = $this->plugin->getBlock($block_id);
+    $form['block_display'] = [
+      '#type' => 'value',
+      '#value' => $block_display
+    ];
     return parent::buildForm($form, $form_state);
   }
 
@@ -72,20 +92,11 @@ class VariantPluginDeleteBlockForm extends ConfirmFormBase {
    * {@inheritdoc}
    */
   public function submitForm(array &$form, FormStateInterface $form_state) {
-    $this->getVariantPlugin()->removeBlock($this->block->getConfiguration()['uuid']);
-    $this->pageVariant->save();
+    $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()]));
-
-    $form_state->setRedirectUrl($this->getCancelUrl());
-  }
-
-  /**
-   * Gets the variant plugin for this page variant entity.
-   *
-   * @return \Drupal\ctools\Plugin\BlockVariantInterface
-   */
-  protected function getVariantPlugin() {
-    return $this->pageVariant->getVariantPlugin();
   }
 
 }
diff --git a/src/PageInterface.php b/src/PageInterface.php
index 08d0cd3..2e8514b 100644
--- a/src/PageInterface.php
+++ b/src/PageInterface.php
@@ -16,6 +16,14 @@ use Drupal\Core\Entity\EntityWithPluginCollectionInterface;
 interface PageInterface extends ConfigEntityInterface, EntityWithPluginCollectionInterface {
 
   /**
+   * Returns the administrative description of the page.
+   *
+   * @return string
+   *   The description of this page.
+   */
+  public function getDescription();
+
+  /**
    * Returns whether the page entity is enabled.
    *
    * @return bool
diff --git a/src/Plugin/DisplayVariant/PageBlockDisplayVariant.php b/src/Plugin/DisplayVariant/PageBlockDisplayVariant.php
index e7b3c36..ce8c9b6 100644
--- a/src/Plugin/DisplayVariant/PageBlockDisplayVariant.php
+++ b/src/Plugin/DisplayVariant/PageBlockDisplayVariant.php
@@ -14,6 +14,7 @@ use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Plugin\ContextAwarePluginInterface;
 use Drupal\Core\Render\Element;
 use Drupal\ctools\Plugin\DisplayVariant\BlockDisplayVariant;
+use Drupal\ctools\Plugin\PluginWizardInterface;
 
 /**
  * Provides a variant plugin that simply contains blocks.
@@ -23,7 +24,7 @@ use Drupal\ctools\Plugin\DisplayVariant\BlockDisplayVariant;
  *   admin_label = @Translation("Block page")
  * )
  */
-class PageBlockDisplayVariant extends BlockDisplayVariant {
+class PageBlockDisplayVariant extends BlockDisplayVariant implements PluginWizardInterface {
 
   /**
    * {@inheritdoc}
@@ -182,6 +183,18 @@ class PageBlockDisplayVariant extends BlockDisplayVariant {
   /**
    * {@inheritdoc}
    */
+  public function getWizardOperations($cached_values) {
+    return [
+      'content' => [
+        'title' => $this->t('Content'),
+        'form' => '\Drupal\page_manager\Form\VariantPluginContentForm',
+      ],
+    ];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function defaultConfiguration() {
     return parent::defaultConfiguration() + [
       'page_title' => '',
diff --git a/src/Tests/PageManagerAdminTest.php b/src/Tests/PageManagerAdminTest.php
index c0112aa..03a7198 100644
--- a/src/Tests/PageManagerAdminTest.php
+++ b/src/Tests/PageManagerAdminTest.php
@@ -55,6 +55,8 @@ class PageManagerAdminTest extends WebTestBase {
    */
   public function testAdmin() {
     $this->doTestAddPage();
+    $this->doTestAccessConditions();
+    $this->doTestSelectionCriteria();
     $this->doTestDisablePage();
     $this->doTestAddVariant();
     $this->doTestAddBlock();
@@ -85,47 +87,155 @@ class PageManagerAdminTest extends WebTestBase {
     $edit = [
       'id' => 'foo',
       'path' => 'admin/foo',
+      'variant_plugin_id' => 'http_status_code',
+      'use_admin_theme' => TRUE,
+      'description' => 'This is our first test page.',
+      // Go through all available steps (we skip them all in doTestSecondPage())
+      'wizard_options[access]' => TRUE,
+      'wizard_options[selection]' => TRUE,
     ];
-    $this->drupalPostForm(NULL, $edit, 'Save');
-    $this->assertText('Label field is required.');
+    $this->drupalPostForm(NULL, $edit, 'Next');
+    $this->assertText('Administrative title field is required.');
 
     // Add a new page with a label.
     $edit += ['label' => 'Foo'];
-    $this->drupalPostForm(NULL, $edit, 'Save');
-    $this->assertRaw(new FormattableMarkup('The %label page has been added.', ['%label' => 'Foo']));
-
-    // Assert that no variant was added by default.
-    $this->drupalGet('admin/structure/page_manager/manage/foo');
-    $this->assertText('There are no variants.');
-
-    // Test that it is available immediately.
-    $this->drupalGet('admin/foo');
-    $this->assertResponse(404);
-    $this->drupalGet('admin/structure/page_manager/manage/foo');
-    $this->clickLink('Add new variant');
-    $this->clickLink('HTTP status code');
+    $this->drupalPostForm(NULL, $edit, 'Next');
+
+    // Test the 'Page Access' step.
+    $this->assertTitle('Page Access | Drupal');
+    $access_path = 'admin/structure/page_manager/add/foo/access';
+    $this->assertUrl($access_path . '?js=nojs');
+    $this->doTestAccessConditions($access_path, FALSE);
+    $this->drupalPostForm(NULL, [], 'Next');
+
+    // Test the 'Selection Criteria' step.
+    $this->assertTitle('Selection Criteria | Drupal');
+    $selection_path = 'admin/structure/page_manager/add/foo/selection';
+    $this->assertUrl($selection_path . '?js=nojs');
+    $this->doTestSelectionCriteria($selection_path, FALSE);
+    $this->drupalPostForm(NULL, [], 'Next');
+
+    // Configure the variant.
     $edit = [
-      'id' => 'http_status_code',
-      'label' => 'Status Code',
+      'page_variant_label' => 'Status Code',
       'variant_settings[status_code]' => 200,
     ];
-    $this->drupalPostForm(NULL, $edit, 'Save');
+    $this->drupalPostForm(NULL, $edit, 'Finish');
+    $this->assertRaw(new FormattableMarkup('Saved the %label Page.', ['%label' => 'Foo']));
 
-    // There is a variant now, so the empty text is no longer visible.
-    $this->assertNoText('There are no variants.');
+    // @todo: assert that we've gone from the add wizard to the edit wizard
 
     $this->drupalGet('admin/foo');
     $this->assertResponse(200);
     $this->assertTitle('Foo | Drupal');
-    $this->drupalGet('admin/structure/page_manager/manage/foo');
-    $this->clickLink('Edit');
-    $this->drupalPostForm(NULL, ['variant_settings[status_code]' => 403], 'Save');
+
+    // Change the status code to 403.
+    $this->drupalGet('admin/structure/page_manager/manage/foo/page_variant__foo-http_status_code-0__general');
+    $edit = [
+      'variant_settings[status_code]' => 403,
+    ];
+    $this->drupalPostForm(NULL, $edit, 'Update');
 
     // Set the weight of the 'Status Code' variant to 10.
+    $this->drupalGet('admin/structure/page_manager/manage/foo/reorder_variants');
+    $edit = [
+      'variants[foo-http_status_code-0][weight]' => 10,
+    ];
+    $this->drupalPostForm(NULL, $edit, 'Update');
+    $this->drupalPostForm(NULL, [], 'Update and save');
+  }
+
+  /**
+   * Tests access conditions step on both add and edit wizard.
+   *
+   * @param string $path
+   *   The path this step is supposed to be at.
+   * @param bool|TRUE $redirect
+   *   Whether or not to redirect to the path.
+   */
+  protected function doTestAccessConditions($path = 'admin/structure/page_manager/manage/foo/access', $redirect = TRUE) {
+    if ($this->getUrl() !== $path && $redirect) {
+      $this->drupalGet($path);
+    }
+
+    $this->assertRaw('No required conditions have been configured.');
+
+    // Configure a new condition.
     $edit = [
-      'variants[http_status_code][weight]' => 10,
+      'conditions' => 'user_role',
+    ];
+    $this->drupalPostForm(NULL, $edit, 'Configure Condition');
+    $this->assertTitle('Add access condition | Drupal');
+    $edit = [
+      'roles[authenticated]' => TRUE,
+    ];
+    $this->drupalPostForm(NULL, $edit, 'Save');
+    $this->assertRaw('The user is a member of Authenticated user');
+    // Make sure we're still on the same wizard.
+    $this->assertUrl($path);
+
+    // Edit the condition.
+    $this->clickLink('Edit');
+    $this->assertTitle('Edit access condition | Drupal');
+    $edit = [
+      'roles[anonymous]' => TRUE,
     ];
     $this->drupalPostForm(NULL, $edit, 'Save');
+    $this->assertRaw('The user is a member of Anonymous user, Authenticated user');
+    $this->assertUrl($path);
+
+    // Delete the condition.
+    $this->clickLink('Delete');
+    $this->assertTitle('Are you sure you want to delete the user_role condition? | Drupal');
+    $this->drupalPostForm(NULL, [], 'Delete');
+    $this->assertRaw('No required conditions have been configured.');
+    $this->assertUrl($path);
+  }
+
+  /**
+   * Tests selection criteria step on both add and edit wizard.
+   *
+   * @param string $path
+   *   The path this step is supposed to be at.
+   * @param bool|TRUE $redirect
+   *   Whether or not to redirect to the path.
+   */
+  protected function doTestSelectionCriteria($path = 'admin/structure/page_manager/manage/foo/page_variant__foo-http_status_code-0__selection', $redirect = TRUE) {
+    if ($this->getUrl() !== $path && $redirect) {
+      $this->drupalGet($path);
+    }
+    $this->assertRaw('No required conditions have been configured.');
+
+    // Configure a new condition.
+    $edit = [
+      'conditions' => 'user_role',
+    ];
+    $this->drupalPostForm(NULL, $edit, 'Configure Condition');
+    $this->assertTitle('Add new selection condition | Drupal');
+    $edit = [
+      'roles[authenticated]' => TRUE,
+    ];
+    $this->drupalPostForm(NULL, $edit, 'Save');
+    $this->assertRaw('The user is a member of Authenticated user');
+    // Make sure we're still on the add wizard (not the edit wizard).
+    $this->assertUrl($path);
+
+    // Edit the condition.
+    $this->clickLink('Edit');
+    $this->assertTitle('Edit selection condition | Drupal');
+    $edit = [
+      'roles[anonymous]' => TRUE,
+    ];
+    $this->drupalPostForm(NULL, $edit, 'Save');
+    $this->assertRaw('The user is a member of Anonymous user, Authenticated user');
+    $this->assertUrl($path);
+
+    // Delete the condition.
+    $this->clickLink('Delete');
+    $this->assertTitle('Are you sure you want to delete the user_role condition? | Drupal');
+    $this->drupalPostForm(NULL, [], 'Delete');
+    $this->assertRaw('No required conditions have been configured.');
+    $this->assertUrl($path);
   }
 
   /**
@@ -152,18 +262,23 @@ class PageManagerAdminTest extends WebTestBase {
    * Tests adding a variant.
    */
   protected function doTestAddVariant() {
-    $this->drupalGet('admin/structure/page_manager/manage/foo');
+    $this->drupalGet('admin/structure/page_manager/manage/foo/general');
 
     // Add a new variant.
-    $this->clickLink('Add new variant');
-    $this->clickLink('Block page');
+    $this->clickLink('Add variant');
     $edit = [
+      'variant_plugin_id' => 'block_display',
       'label' => 'First',
       'id' => 'block_page',
+    ];
+    $this->drupalPostForm(NULL, $edit, 'Create variant');
+
+    // Set the page title.
+    $this->drupalGet('admin/structure/page_manager/manage/foo/page_variant__block_page__general');
+    $edit = [
       'variant_settings[page_title]' => 'Example title',
     ];
-    $this->drupalPostForm(NULL, $edit, 'Save');
-    $this->assertRaw(new FormattableMarkup('Saved the %label variant.', ['%label' => 'First']));
+    $this->drupalPostForm(NULL, $edit, 'Update and save');
 
     // Test that the variant is still used but empty.
     $this->drupalGet('admin/foo');
@@ -177,8 +292,7 @@ class PageManagerAdminTest extends WebTestBase {
    * Tests adding a block to a variant.
    */
   protected function doTestAddBlock() {
-    $this->drupalGet('admin/structure/page_manager/manage/foo');
-    $this->clickLink('Edit');
+    $this->drupalGet('admin/structure/page_manager/manage/foo/page_variant__block_page__content');
     // Add a block to the variant.
     $this->clickLink('Add new block');
 
@@ -190,6 +304,7 @@ class PageManagerAdminTest extends WebTestBase {
       'region' => 'top',
     ];
     $this->drupalPostForm(NULL, $edit, 'Add block');
+    $this->drupalPostForm(NULL, [], 'Update and save');
 
     // Test that the block is displayed.
     $this->drupalGet('admin/foo');
@@ -221,20 +336,20 @@ class PageManagerAdminTest extends WebTestBase {
       'id' => 'second',
       'label' => 'Second',
       'path' => 'second',
+      'variant_plugin_id' => 'block_display',
     ];
-    $this->drupalPostForm(NULL, $edit, 'Save');
-    $this->assertRaw(new FormattableMarkup('The %label page has been added.', ['%label' => 'Second']));
+    $this->drupalPostForm(NULL, $edit, 'Next');
 
-    // Add a variant.
-    $this->clickLink('Add new variant');
-    $this->clickLink('Block page');
+    // Configure the variant.
     $edit = [
-      'label' => 'Second variant',
-      'id' => 'second_block_page',
+      'page_variant_label' => 'Second variant',
       'variant_settings[page_title]' => 'Second title',
     ];
-    $this->drupalPostForm(NULL, $edit, 'Save');
-    $this->assertRaw(new FormattableMarkup('Saved the %label variant.', ['%label' => 'Second variant']));
+    $this->drupalPostForm(NULL, $edit, 'Next');
+
+    // We're now on the content step, but we don't need to add any blocks.
+    $this->drupalPostForm(NULL, [], 'Finish');
+    $this->assertRaw(new FormattableMarkup('Saved the %label Page.', ['%label' => 'Second']));
 
     // Visit both pages, make sure that they do not interfere with each other.
     $this->drupalGet('admin/foo');
@@ -247,13 +362,13 @@ class PageManagerAdminTest extends WebTestBase {
    * Tests editing a block.
    */
   protected function doTestEditBlock() {
-    $this->drupalGet('admin/structure/page_manager/manage/foo');
-    $this->clickLink('Edit');
+    $this->drupalGet('admin/structure/page_manager/manage/foo/page_variant__block_page__content');
     $this->clickLink('Edit');
     $edit = [
       'settings[label]' => 'Updated block label',
     ];
     $this->drupalPostForm(NULL, $edit, 'Update block');
+    $this->drupalPostForm(NULL, [], 'Update and save');
     // Test that the block is displayed.
     $this->drupalGet('admin/foo');
     $this->assertResponse(200);
@@ -274,23 +389,20 @@ class PageManagerAdminTest extends WebTestBase {
     }
 
     $block_config = $block->getConfiguration();
-    $this->drupalGet('admin/structure/page_manager/manage/foo');
-    $this->clickLink('Edit');
-    $this->assertTitle('Edit First variant | Drupal');
+    $this->drupalGet('admin/structure/page_manager/manage/foo/page_variant__block_page__content');
 
-    $this->assertOptionSelected('edit-variant-plugin-blocks-' . $block_config['uuid'] . '-region', 'top');
-    $this->assertOptionSelected('edit-variant-plugin-blocks-' . $block_config['uuid'] . '-weight', 0);
+    $this->assertOptionSelected('edit-blocks-' . $block_config['uuid'] . '-region', 'top');
+    $this->assertOptionSelected('edit-blocks-' . $block_config['uuid'] . '-weight', 0);
 
-    $form_name = 'variant_plugin[blocks][' . $block_config['uuid'] . ']';
+    $form_name = 'blocks[' . $block_config['uuid'] . ']';
     $edit = [
       $form_name . '[region]' => 'bottom',
       $form_name . '[weight]' => -10,
     ];
-    $this->drupalPostForm(NULL, $edit, 'Save');
-    $this->assertRaw(new FormattableMarkup('Saved the %label variant.', ['%label' => 'First']));
-    $this->clickLink('Edit');
-    $this->assertOptionSelected('edit-variant-plugin-blocks-' . $block_config['uuid'] . '-region', 'bottom');
-    $this->assertOptionSelected('edit-variant-plugin-blocks-' . $block_config['uuid'] . '-weight', -10);
+    $this->drupalPostForm(NULL, $edit, 'Update');
+    $this->assertOptionSelected('edit-blocks-' . $block_config['uuid'] . '-region', 'bottom');
+    $this->assertOptionSelected('edit-blocks-' . $block_config['uuid'] . '-weight', -10);
+    $this->drupalPostForm(NULL, [], 'Update and save');
   }
 
   /**
@@ -307,10 +419,13 @@ class PageManagerAdminTest extends WebTestBase {
     }
     $this->assertEqual($expected, $links);
 
+    $this->drupalGet('admin/structure/page_manager/manage/foo/general');
+    $this->clickLink('Reorder variants');
     $edit = [
-      'variants[http_status_code][weight]' => -10,
+      'variants[foo-http_status_code-0][weight]' => -10,
     ];
-    $this->drupalPostForm('admin/structure/page_manager/manage/foo', $edit, 'Save');
+    $this->drupalPostForm(NULL, $edit, 'Update');
+    $this->drupalPostForm(NULL, [], 'Update and save');
     $this->drupalGet('admin/foo');
     $this->assertResponse(403);
   }
@@ -325,7 +440,7 @@ class PageManagerAdminTest extends WebTestBase {
       'id' => 'bar',
       'path' => 'admin/foo',
     ];
-    $this->drupalPostForm('admin/structure/page_manager/add', $edit, 'Save');
+    $this->drupalPostForm('admin/structure/page_manager/add', $edit, 'Next');
     $this->assertText('The page path must be unique.');
     $this->drupalGet('admin/structure/page_manager');
     $this->assertNoText('Bar');
@@ -339,10 +454,11 @@ class PageManagerAdminTest extends WebTestBase {
     $this->drupalGet('admin/foo');
     $this->assertTheme('classy');
 
+    $this->drupalGet('admin/structure/page_manager/manage/foo/general');
     $edit = [
       'use_admin_theme' => FALSE,
     ];
-    $this->drupalPostForm('admin/structure/page_manager/manage/foo', $edit, 'Save');
+    $this->drupalPostForm(NULL, $edit, 'Update and save');
     $this->drupalGet('admin/foo');
     $this->assertTheme('bartik');
 
@@ -354,11 +470,12 @@ class PageManagerAdminTest extends WebTestBase {
    * Tests removing a variant.
    */
   protected function doTestRemoveVariant() {
-    $this->drupalGet('admin/structure/page_manager/manage/foo');
-    $this->clickLink('Delete');
-    $this->assertRaw(new FormattableMarkup('Are you sure you want to delete %label?', ['%label' => 'Status Code']));
+    $this->drupalGet('admin/structure/page_manager/manage/foo/page_variant__foo-http_status_code-0__general');
+    $this->clickLink('Delete this variant');
+    $this->assertRaw('Are you sure you want to delete this variant?');
     $this->drupalPostForm(NULL, [], 'Delete');
     $this->assertRaw(new FormattableMarkup('The variant %label has been removed.', ['%label' => 'Status Code']));
+    $this->drupalPostForm(NULL, [], 'Update and save');
   }
 
   /**
@@ -376,12 +493,12 @@ class PageManagerAdminTest extends WebTestBase {
     }
     $this->assertEqual($expected, $links);
 
-    $this->drupalGet('admin/structure/page_manager/manage/foo');
-    $this->clickLink('Edit');
+    $this->drupalGet('admin/structure/page_manager/manage/foo/page_variant__block_page__content');
     $this->clickLink('Delete');
     $this->assertRaw(new FormattableMarkup('Are you sure you want to delete the block %label?', ['%label' => 'Updated block label']));
     $this->drupalPostForm(NULL, [], 'Delete');
     $this->assertRaw(new FormattableMarkup('The block %label has been removed.', ['%label' => 'Updated block label']));
+    $this->drupalPostForm(NULL, [], 'Update and save');
 
     // Assert that the block is now gone.
     $this->drupalGet('admin/foo');
@@ -394,8 +511,7 @@ class PageManagerAdminTest extends WebTestBase {
    * Tests adding a block with #ajax to a variant.
    */
   protected function doTestAddBlockWithAjax() {
-    $this->drupalGet('admin/structure/page_manager/manage/foo');
-    $this->clickLink('Edit');
+    $this->drupalGet('admin/structure/page_manager/manage/foo/page_variant__block_page__content');
     // Add a block to the variant.
     $this->clickLink('Add new block');
     $this->clickLink('Page Manager Test Block');
@@ -403,6 +519,7 @@ class PageManagerAdminTest extends WebTestBase {
       'region' => 'top',
     ];
     $this->drupalPostForm(NULL, $edit, 'Add block');
+    $this->drupalPostForm(NULL, [], 'Update and save');
 
     // Test that the block is displayed.
     $this->drupalGet('admin/foo');
@@ -430,20 +547,16 @@ class PageManagerAdminTest extends WebTestBase {
       'label' => 'existing',
       'id' => 'existing',
       'path' => 'admin',
+      'variant_plugin_id' => 'http_status_code',
     ];
-    $this->drupalPostForm(NULL, $edit, 'Save');
-
-    // Regular result is displayed.
-    $this->assertText('The existing page has been added');
+    $this->drupalPostForm(NULL, $edit, 'Next');
 
-    $this->clickLink('Add new variant');
-    $this->clickLink('HTTP status code');
+    // Configure the variant.
     $edit = [
-      'id' => 'http_status_code',
-      'label' => 'Status Code',
+      'page_variant_label' => 'Status Code',
       'variant_settings[status_code]' => 404,
     ];
-    $this->drupalPostForm(NULL, $edit, 'Save');
+    $this->drupalPostForm(NULL, $edit, 'Finish');
 
     // Ensure the existing path leads to the new page.
     $this->drupalGet('admin');
@@ -476,32 +589,33 @@ class PageManagerAdminTest extends WebTestBase {
    */
   public function testExistingRoutes() {
     // Test that the page without placeholder is accessible.
+    $this->drupalGet('admin/structure/page_manager/add');
     $edit = [
       'label' => 'Placeholder test 2',
       'id' => 'placeholder2',
       'path' => '/page-manager-test',
+      'variant_plugin_id' => 'http_status_code',
     ];
-    $this->drupalPostForm('admin/structure/page_manager/add', $edit, 'Save');
-    $this->drupalGet('page-manager-test');
-    // Without a single variant, it will fall through to the original.
-    $this->assertResponse(200);
-
-    $this->drupalGet('admin/structure/page_manager/manage/placeholder2');
-    $this->clickLink('Add new variant');
-    $this->clickLink('HTTP status code');
+    $this->drupalPostForm(NULL, $edit, 'Next');
     $edit = [
-      'id' => 'http_status_code',
-      'label' => 'Status Code',
-      'variant_settings[status_code]' => 404,
+      'variant_settings[status_code]' => 418,
     ];
-    $this->drupalPostForm(NULL, $edit, 'Save');
+    $this->drupalPostForm(NULL, $edit, 'Finish');
     $this->drupalGet('page-manager-test');
-    $this->assertResponse(404);
+    $this->assertResponse(418);
 
     // Test that the page test is accessible.
     $page_string = 'test-page';
     $this->drupalGet('page-manager-test/' . $page_string);
     $this->assertResponse(200);
+
+    // Without a single variant, it will fall through to the original.
+    $this->drupalGet('admin/structure/page_manager/manage/placeholder2/page_variant__placeholder2-http_status_code-0__general');
+    $this->clickLink('Delete this variant');
+    $this->drupalPostForm(NULL, [], 'Delete');
+    $this->drupalPostForm(NULL, [], 'Update and save');
+    $this->drupalGet('page-manager-test');
+    $this->assertResponse(200);
   }
 
   /**
diff --git a/src/Tests/PageManagerTranslationIntegrationTest.php b/src/Tests/PageManagerTranslationIntegrationTest.php
index 1c5b47c..f094c51 100644
--- a/src/Tests/PageManagerTranslationIntegrationTest.php
+++ b/src/Tests/PageManagerTranslationIntegrationTest.php
@@ -60,20 +60,21 @@ class PageManagerTranslationIntegrationTest extends ContentTranslationTestBase {
     $this->clickLink('Translate');
     $this->assertResponse(200);
 
-    // Create a new page entity to take over node pages.
+    // Create a new variant.
+    $this->drupalGet('admin/structure/page_manager/manage/node_view/general');
     $edit = [
-      'label' => 'Node View',
-      'id' => 'node_view',
-      'path' => 'node/%',
+      'variant_plugin_id' => 'http_status_code',
+      'id' => 'node_view_http_status_code',
+      'label' => 'HTTP Status Code',
     ];
-    $this->drupalPostForm('admin/structure/page_manager/add', $edit, 'Save');
+    $this->drupalPostForm('admin/structure/page_manager/manage/node_view/add_variant', $edit, 'Create variant');
 
-    // Create a new variant.
+    // Configure the variant.
+    $this->drupalGet('admin/structure/page_manager/manage/node_view/page_variant__node_view_http_status_code__general');
     $edit = [
-      'id' => 'http_status_code',
       'variant_settings[status_code]' => 200,
     ];
-    $this->drupalPostForm('admin/structure/page_manager/manage/node_view/add/http_status_code', $edit, 'Save');
+    $this->drupalPostForm(NULL, $edit, 'Update and save');
 
     $this->drupalGet('node/' . $node->id());
     $this->assertResponse(200);
diff --git a/src/Tests/PageNodeSelectionTest.php b/src/Tests/PageNodeSelectionTest.php
index 686f0ab..ca7d8ef 100644
--- a/src/Tests/PageNodeSelectionTest.php
+++ b/src/Tests/PageNodeSelectionTest.php
@@ -55,11 +55,20 @@ class PageNodeSelectionTest extends WebTestBase {
 
     // Create a new variant to always return 404, the node_view page exists by
     // default.
+    $this->drupalGet('admin/structure/page_manager/manage/node_view/general');
+    $edit = [
+      'variant_plugin_id' => 'http_status_code',
+      'id' => 'node_view_http_status_code',
+      'label' => 'HTTP Status Code',
+    ];
+    $this->drupalPostForm('admin/structure/page_manager/manage/node_view/add_variant', $edit, 'Create variant');
+
+    // Configure the variant.
+    $this->drupalGet('admin/structure/page_manager/manage/node_view/page_variant__node_view_http_status_code__general');
     $edit = [
-      'id' => 'http_status_code',
       'variant_settings[status_code]' => 404,
     ];
-    $this->drupalPostForm('admin/structure/page_manager/manage/node_view/add/http_status_code', $edit, 'Save');
+    $this->drupalPostForm(NULL, $edit, 'Update and save');
 
     $this->drupalGet('node/' . $node1->id());
     $this->assertResponse(404);
@@ -70,43 +79,49 @@ class PageNodeSelectionTest extends WebTestBase {
     $this->assertNoText($node2->label());
 
     // Add a new variant.
-    $this->drupalGet('admin/structure/page_manager/manage/node_view');
-    $this->clickLink('Add new variant');
-    $this->clickLink('Block page');
+    $this->drupalGet('admin/structure/page_manager/manage/node_view/general');
     $edit = [
+      'variant_plugin_id' => 'block_display',
       'id' => 'block_page_first',
       'label' => 'First',
     ];
-    $this->drupalPostForm(NULL, $edit, 'Save');
+    $this->drupalPostForm('admin/structure/page_manager/manage/node_view/add_variant', $edit, 'Create variant');
 
-    // Add the entity view block.
-    $this->clickLink('Add new block');
-    $this->clickLink('Entity view (Content)');
+    // Set the page title to the node title.
+    $this->drupalGet('admin/structure/page_manager/manage/node_view/page_variant__block_page_first__general');
     $edit = [
-      'region' => 'top',
-      'settings[label_display]' => FALSE,
+      'variant_settings[page_title]' => '[node:title]',
     ];
-    $this->drupalPostForm(NULL, $edit, 'Add block');
+    $this->drupalPostForm(NULL, $edit, 'Update');
 
     // Add a node bundle condition for articles.
-    $this->clickLink('Add new selection condition');
-    $this->clickLink('Node Bundle');
+    $this->drupalGet('admin/structure/page_manager/manage/node_view/page_variant__block_page_first__selection');
     $edit = [
-      'condition[bundles][article]' => TRUE,
+      'conditions' => 'node_type',
     ];
-    $this->drupalPostForm(NULL, $edit, 'Add selection condition');
-
-    // Set the page title to the node title.
+    $this->drupalPostForm(NULL, $edit, 'Configure Condition');
     $edit = [
-      'variant_settings[page_title]' => '[node:title]',
+      'bundles[article]' => TRUE,
     ];
     $this->drupalPostForm(NULL, $edit, 'Save');
 
     // Set the weight of block_page.
+    $this->clickLink('Reorder variants');
     $edit = [
       'variants[block_page_first][weight]' => -10,
     ];
-    $this->drupalPostForm(NULL, $edit, 'Save');
+    $this->drupalPostForm(NULL, $edit, 'Update');
+
+    // Add the entity view block.
+    $this->drupalGet('admin/structure/page_manager/manage/node_view/page_variant__block_page_first__content');
+    $this->clickLink('Add new block');
+    $this->clickLink('Entity view (Content)');
+    $edit = [
+      'region' => 'top',
+      'settings[label_display]' => FALSE,
+    ];
+    $this->drupalPostForm(NULL, $edit, 'Add block');
+    $this->drupalPostForm(NULL, [], 'Update and save');
 
     // The page node will 404, but the article node will display the variant.
     $this->drupalGet('node/' . $node1->id());
diff --git a/src/Tests/PagePlaceholderTest.php b/src/Tests/PagePlaceholderTest.php
index a94d361..d3d3424 100644
--- a/src/Tests/PagePlaceholderTest.php
+++ b/src/Tests/PagePlaceholderTest.php
@@ -41,19 +41,21 @@ class PagePlaceholderTest extends WebTestBase {
     $this->assertText('Hello World! Page ' . $page_string);
 
     // Create a new page entity with the same path as in the test module.
+    $this->drupalGet('admin/structure/page_manager/add');
     $edit = [
       'label' => 'Placeholder test',
       'id' => 'placeholder',
       'path' => '/page-manager-test/%',
+      'variant_plugin_id' => 'http_status_code',
     ];
-    $this->drupalPostForm('admin/structure/page_manager/add', $edit, 'Save');
+    $this->drupalPostForm(NULL, $edit, 'Next');
 
-    // Create a new variant.
+    // Configure the variant.
     $edit = [
-      'id' => 'http_status_code',
+      'page_variant_label' => 'HTTP Status Code',
       'variant_settings[status_code]' => 200,
     ];
-    $this->drupalPostForm('admin/structure/page_manager/manage/placeholder/add/http_status_code', $edit, 'Save');
+    $this->drupalPostForm(NULL, $edit, 'Finish');
 
     // Access the page callback again and check that now the text is not there.
     $this->drupalGet('page-manager-test/' . $page_string);
diff --git a/src/Wizard/PageAddWizard.php b/src/Wizard/PageAddWizard.php
new file mode 100644
index 0000000..87b5833
--- /dev/null
+++ b/src/Wizard/PageAddWizard.php
@@ -0,0 +1,66 @@
+<?php
+/**
+ * @file
+ * Contains \Drupal\page_manager\Wizard\PageAddWizard.
+ */
+
+namespace Drupal\page_manager\Wizard;
+
+use Drupal\Core\Display\ContextAwareVariantInterface;
+use Drupal\ctools\Plugin\PluginWizardInterface;
+
+class PageAddWizard extends PageWizardBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getRouteName() {
+    return 'entity.page.add_step_form';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getOperations($cached_values) {
+    $operations = parent::getOperations($cached_values);
+
+    // Add steps for selection and creating the first variant.
+    $operations['selection'] = [
+      'title' => $this->t('Selection Criteria'),
+      'form' => '\Drupal\page_manager\Form\PageVariantSelectionForm',
+    ];
+    $operations['display_variant'] = [
+      'title' => $this->t('Configure Variant'),
+      'form' => '\Drupal\page_manager\Form\PageVariantConfigureForm',
+    ];
+
+    // Hide any optional steps that aren't selected.
+    $optional_steps = ['access', 'contexts', 'selection'];
+    foreach ($optional_steps as $step_name) {
+      if (empty($cached_values['wizard_options'][$step_name])) {
+        unset($operations[$step_name]);
+      }
+    }
+
+    // Add any wizard operations from the plugin itself.
+    if (!empty($cached_values['page_variant'])) {
+      /** @var \Drupal\page_manager\PageVariantInterface $page_variant */
+      $page_variant = $cached_values['page_variant'];
+      $variant_plugin = $page_variant->getVariantPlugin();
+      if ($variant_plugin instanceof PluginWizardInterface) {
+        if ($variant_plugin instanceof ContextAwareVariantInterface) {
+          $variant_plugin->setContexts($page_variant->getContexts());
+        }
+        $cached_values['plugin'] = $variant_plugin;
+        foreach ($variant_plugin->getWizardOperations($cached_values) as $name => $operation) {
+          $operation['values']['plugin'] = $variant_plugin;
+          $operation['submit'][] = '::submitVariantStep';
+          $operations[$name] = $operation;
+        }
+      }
+    }
+
+    return $operations;
+  }
+
+}
diff --git a/src/Wizard/PageEditWizard.php b/src/Wizard/PageEditWizard.php
new file mode 100644
index 0000000..97a391b
--- /dev/null
+++ b/src/Wizard/PageEditWizard.php
@@ -0,0 +1,260 @@
+<?php
+/**
+ * @file
+ * Contains \Drupal\page_manager\Wizard\PageEditWizard.
+ */
+
+namespace Drupal\page_manager\Wizard;
+
+use Drupal\Core\Display\ContextAwareVariantInterface;
+use Drupal\Core\Form\FormBuilderInterface;
+use Drupal\Core\Form\FormInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Url;
+use Drupal\ctools\Plugin\PluginWizardInterface;
+use Drupal\page_manager\PageInterface;
+use Drupal\page_manager\PageVariantInterface;
+
+class PageEditWizard extends PageWizardBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getOperations($cached_values) {
+    $operations = parent::getOperations($cached_values);
+
+    /** @var $page \Drupal\page_manager\Entity\Page */
+    $page = $cached_values['page'];
+
+    if (!empty($page)) {
+      // Get variants and re-sort by weight or remove variants if the user
+      // has edited the variant.
+      $variants = $page->getVariants();
+      if (!empty($cached_values['deleted_variants'])) {
+        foreach ($cached_values['deleted_variants'] as $page_variant) {
+          if (isset($variants[$page_variant->id()])) {
+            unset($variants[$page_variant->id()]);
+          }
+        }
+      }
+      // Suppress errors because of https://bugs.php.net/bug.php?id=50688.
+      @uasort($variants, '\Drupal\page_manager\Entity\PageVariant::sort');
+
+      foreach ($variants as $page_variant) {
+        foreach ($this->getVariantOperations($page_variant, $cached_values) as $name => $operation) {
+          $operation['values']['page_variant'] = $page_variant;
+          $operation['breadcrumbs'] = [
+            $this->t('Variants'),
+            $page_variant->label() ?: $this->t('Variant'),
+          ];
+          $operations['page_variant__' . $page_variant->id() . '__' . $name] = $operation;
+        }
+      }
+    }
+
+    return $operations;
+  }
+
+  /**
+   * Get operations for the variant.
+   *
+   * @param \Drupal\page_manager\PageVariantInterface $page_variant
+   *   The page variant entity.
+   * @param mixed $cached_values
+   *   The cached values.
+   *
+   * @returns array
+   */
+  protected function getVariantOperations(PageVariantInterface $page_variant, $cached_values) {
+    $operations = [];
+    $operations['general'] = [
+      'title' => $this->t('General'),
+      'form' => '\Drupal\page_manager\Form\PageVariantConfigureForm',
+    ];
+    $operations['selection'] = [
+      'title' => $this->t('Selection Criteria'),
+      'form' => '\Drupal\page_manager\Form\PageVariantSelectionForm',
+    ];
+
+    // Add any wizard operations from the plugin itself.
+    $variant_plugin = $page_variant->getVariantPlugin();
+    if ($variant_plugin instanceof PluginWizardInterface) {
+      if ($variant_plugin instanceof ContextAwareVariantInterface) {
+        $variant_plugin->setContexts($page_variant->getContexts());
+      }
+      $cached_values['plugin'] = $variant_plugin;
+      foreach ($variant_plugin->getWizardOperations($cached_values) as $name => $operation) {
+        $operation['values']['plugin'] = $variant_plugin;
+        $operation['submit'][] = '::submitVariantStep';
+        $operations[$name] = $operation;
+      }
+    }
+
+    return $operations;
+  }
+
+  /**
+   * Get action links for the page.
+   *
+   * @return array
+   *   An array of associative arrays with the following keys:
+   *   - title: The link text
+   *   - url: A URL object
+   */
+  protected function getPageActionLinks(PageInterface $page) {
+    $links = [];
+
+    $links[] = [
+      'title' => $this->t('Delete page'),
+      'url' => new Url('entity.page.delete_form', [
+        'page' => $this->getMachineName(),
+      ]),
+    ];
+
+    $links[] = [
+      'title' => $this->t('Add variant'),
+      'url' => new Url('entity.page_variant.add_form', [
+        'machine_name' => $this->getMachineName(),
+      ]),
+    ];
+
+    $links[] = [
+      'title' => $this->t('Reorder variants'),
+      'url' => new Url('entity.page.reorder_variants_form', [
+        'machine_name' => $this->getMachineName(),
+      ]),
+    ];
+
+    return $links;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function customizeForm(array $form, FormStateInterface $form_state) {
+    $cached_values = $form_state->getTemporaryValue('wizard');
+    /** @var $page \Drupal\page_manager\Entity\Page */
+    $page = $cached_values['page'];
+
+    // The page actions.
+    $form['wizard_actions'] = [
+      '#theme' => 'links',
+      '#links' => [],
+      '#attributes' => [
+        'class' => ['inline'],
+      ]
+    ];
+    foreach ($this->getPageActionLinks($page) as $action) {
+      $form['wizard_actions']['#links'][] = $action + [
+        'attributes' => [
+          'class' => 'use-ajax',
+          'data-dialog-type' => 'modal',
+        ],
+      ];
+    }
+
+    // The tree of wizard steps.
+    $form['wizard_tree'] = [
+      '#theme' => ['page_manager_wizard_tree'],
+      '#wizard' => $this,
+      '#cached_values' => $form_state->getTemporaryValue('wizard'),
+    ];
+
+    $form['#theme'] = 'page_manager_wizard_form';
+    $form['#attached']['library'][] = 'page_manager/admin';
+    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['finish'] = [
+      '#type' => 'submit',
+      '#value' => $this->t('Update and save'),
+      '#button_type' => 'primary',
+      '#validate' => [
+        '::populateCachedValues',
+        [$form_object, 'validateForm'],
+      ],
+      '#submit' => [
+        [$form_object, 'submitForm'],
+      ],
+    ];
+
+    // Add any submit or validate functions for the step and the global ones.
+    foreach (['submit', '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['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['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) {
+    $cached_values = $form_state->getTemporaryValue('wizard');
+    // Normally, the wizard only saves the data when the 'Next' button is
+    // clicked, but we want to save the data always when editing.
+    $this->getTempstore()->set($this->getMachineName(), $cached_values);
+  }
+
+  /**
+   * @inheritDoc
+   */
+  public function finish(array &$form, FormStateInterface $form_state) {
+    parent::finish($form, $form_state);
+
+    $cached_values = $form_state->getTemporaryValue('wizard');
+
+    // Delete any of the variants marked for deletion.
+    if (!empty($cached_values['deleted_variants'])) {
+      foreach ($cached_values['deleted_variants'] as $page_variant) {
+        $page_variant->delete();
+      }
+    }
+  }
+
+}
diff --git a/src/Wizard/PageWizardBase.php b/src/Wizard/PageWizardBase.php
new file mode 100644
index 0000000..c66a1c2
--- /dev/null
+++ b/src/Wizard/PageWizardBase.php
@@ -0,0 +1,89 @@
+<?php
+/**
+ * @file
+ * Contains \Drupal\page_manager\Wizard\PageWizardBase.
+ */
+
+namespace Drupal\page_manager\Wizard;
+
+use Drupal\Core\Form\FormInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\ctools\Wizard\EntityFormWizardBase;
+use Drupal\page_manager\Access\PageManagerPluginAccess;
+
+class PageWizardBase extends EntityFormWizardBase {
+
+  public function initValues() {
+    $cached_values = parent::initValues();
+    $cached_values['access'] = new PageManagerPluginAccess();
+    return $cached_values;
+  }
+
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getEntityType() {
+    return 'page';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function exists() {
+    return '\Drupal\page_manager\Entity\Page::load';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getWizardLabel() {
+    return $this->t('Page Manager');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getMachineLabel() {
+    return $this->t('Administrative title');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getOperations($cached_values) {
+    $operations = [];
+    $operations['general'] = [
+      'title' => $this->t('Page Information'),
+      'form' => '\Drupal\page_manager\Form\PageGeneralForm',
+    ];
+    $operations['access'] = [
+      'title' => $this->t('Page Access'),
+      'form' => '\Drupal\page_manager\Form\PageAccessForm',
+    ];
+    $operations['contexts'] = [
+      'title' => $this->t('Configure Contexts'),
+      'form' => '\Drupal\page_manager\Form\PageContextsForm',
+    ];
+
+    return $operations;
+  }
+
+  /**
+   * Submission callback for the variant plugin steps.
+   */
+  public function submitVariantStep(array &$form, FormStateInterface $form_state) {
+    $cached_values = $form_state->getTemporaryValue('wizard');
+    /** @var \Drupal\page_manager\PageVariantInterface $page_variant */
+    $page_variant = $cached_values['page_variant'];
+    /** @var \Drupal\Core\Display\VariantInterface $plugin */
+    $plugin = $cached_values['plugin'];
+
+    // Make sure the variant plugin on the page variant gets the configuration
+    // from the 'plugin' which should have been setup by the variant's steps.
+    if (!empty($plugin) && !empty($page_variant)) {
+      $page_variant->getVariantPlugin()->setConfiguration($plugin->getConfiguration());
+    }
+  }
+
+}
diff --git a/src/Wizard/RouteParameters.php b/src/Wizard/RouteParameters.php
new file mode 100644
index 0000000..f43b83a
--- /dev/null
+++ b/src/Wizard/RouteParameters.php
@@ -0,0 +1,84 @@
+<?php
+/**
+ * @file
+ * Contains \Drupal\page_manager\Wizard\RouteParameters.
+ */
+
+namespace Drupal\page_manager\Wizard;
+
+
+use Drupal\Core\Ajax\AjaxResponse;
+use Drupal\Core\Ajax\CloseModalDialogCommand;
+use Drupal\Core\Ajax\RedirectCommand;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\ctools\Ajax\OpenModalWizardCommand;
+use Drupal\ctools\Wizard\FormWizardBase;
+
+class RouteParameters extends FormWizardBase {
+
+  /**
+   * The parameter to configure.
+   *
+   * @var string
+   */
+  protected $parameter;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getOperations() {
+    return [
+      'assign' => [
+        'title' => $this->t('Assign Parameter Context'),
+        'form' => 'Drupal\page_manager\Form\ParameterAssignContextForm',
+      ],
+      'settings' => [
+        'title' => $this->t('Parameter Settings'),
+        'form' => 'Drupal\page_manager\Form\ParameterSettingsForm',
+      ],
+    ];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getRouteName() {
+    return 'page_manager.route.parameters.configure';
+  }
+
+  /**
+   * Override to get the parameter from the URL and make it available to steps.
+   */
+  public function buildForm(array $form, FormStateInterface $form_state, $parameter = NULL) {
+    $this->parameter = $parameter;
+    return parent::buildForm($form, $form_state);
+  }
+
+  public function getNextParameters($cached_values) {
+    $parameters = parent::getNextParameters($cached_values);
+    $parameters['parameter'] = $this->parameter;
+    return $parameters;
+  }
+
+  public function getPreviousParameters($cached_values) {
+    $parameters = parent::getPreviousParameters($cached_values);
+    $parameters['parameter'] = $this->parameter;
+    return $parameters;
+  }
+
+  /**
+   * Save the values to the tempstore.
+   */
+  public function finish(array &$form, FormStateInterface $form_state) {
+    $this->getTempstore()->set($this->getMachineName(), $form_state->getTemporaryValue('wizard'));
+  }
+
+  public function ajaxFinish(array $form, FormStateInterface $form_state) {
+    $cached_values = $form_state->getTemporaryValue('wizard');
+    $response = new AjaxResponse();
+    $response->addCommand(new RedirectCommand($this->url('entity.page.edit_form', ['machine_name' => $cached_values['id'], 'step' => 'parameters'])));
+    $response->addCommand(new CloseModalDialogCommand());
+    return $response;
+  }
+
+}
diff --git a/templates/page-manager-wizard-form.html.twig b/templates/page-manager-wizard-form.html.twig
new file mode 100644
index 0000000..62fb633
--- /dev/null
+++ b/templates/page-manager-wizard-form.html.twig
@@ -0,0 +1,32 @@
+{#
+/**
+ * @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="page-manager-wizard">
+  <div class="page-manager-wizard-actions">
+    {{ form.wizard_actions }}
+  </div>
+  <div class="page-manager-wizard-main clearfix">
+    <div class="page-manager-wizard-tree">
+      {{ form.wizard_tree }}
+    </div>
+    <div class="page-manager-wizard-form">
+      {{ form|without('wizard_actions', 'wizard_tree', 'actions') }}
+    </div>
+  </div>
+
+  <div class="page-manager-wizard-form-actions">
+    {{ form.actions }}
+  </div>
+</div>
+
diff --git a/templates/page-manager-wizard-tree.html.twig b/templates/page-manager-wizard-tree.html.twig
new file mode 100644
index 0000000..0a00bdf
--- /dev/null
+++ b/templates/page-manager-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 page_manager %}
+
+{#
+  We call a macro which calls itself to render the full tree.
+  @see http://twig.sensiolabs.org/doc/tags/macro.html
+#}
+{{ page_manager.wizard_tree(tree, step, 0) }}
+
+{% macro wizard_tree(items, step, menu_level) %}
+  {% import _self as page_manager %}
+  {% 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 %}
+          {{ page_manager.wizard_tree(item.children, step, menu_level + 1) }}
+        {% endif %}
+      </li>
+    {% endfor %}
+    </ul>
+  {% endif %}
+{% endmacro %}
\ No newline at end of file
diff --git a/tests/src/Unit/PageTest.php b/tests/src/Unit/PageTest.php
index ca7ebfc..52286e7 100644
--- a/tests/src/Unit/PageTest.php
+++ b/tests/src/Unit/PageTest.php
@@ -29,10 +29,12 @@ class PageTest extends UnitTestCase {
   public function testGetVariants() {
     $variant1 = $this->prophesize(PageVariantInterface::class);
     $variant1->id()->willReturn('variant1');
-    $variant1->getWeight()->willReturn(0);
+    $variant1->label()->willReturn('Variant 1');;
+    $variant1->weight = 0;
     $variant2 = $this->prophesize(PageVariantInterface::class);
     $variant2->id()->willReturn('variant2');
-    $variant2->getWeight()->willReturn(-10);
+    $variant2->label()->willReturn('Variant 2');;
+    $variant2->weight = -10;
 
     $entity_storage = $this->prophesize(EntityStorageInterface::class);
     $entity_storage
