diff --git a/config/schema/page_manager.schema.yml b/config/schema/page_manager.schema.yml
index 0cc98c5..cd612af 100644
--- a/config/schema/page_manager.schema.yml
+++ b/config/schema/page_manager.schema.yml
@@ -46,6 +46,42 @@ page_manager.page.*:
               type: string
               label: 'Context value'
 
+page_manager.page_variant.*.*:
+  type: config_entity
+  label: 'Page variant configuration'
+  mapping:
+    id:
+      type: string
+      label: 'ID'
+    label:
+      type: label
+      label: 'Label'
+    weight:
+      type: integer
+      label: 'Weight'
+    uuid:
+      type: string
+      label: 'UUID'
+    variant:
+      type: string
+      label: 'Display variant'
+    variant_settings:
+      type: display_variant.plugin.[%parent.variant]
+    page:
+      type: string
+      label: 'Parent page'
+    selection_criteria:
+      type: sequence
+      label: 'Selection criteria'
+      sequence:
+        - type: condition.plugin.[id]
+          label: 'Selection condition'
+    selection_logic:
+      type: string
+      label: 'Selection logic'
+    contexts:
+      type: mapping
+
 page_manager.block_plugin.*:
   type: block.settings.[id]
   mapping:
@@ -66,7 +102,7 @@ page_manager.block_plugin.*:
 
 display_variant.plugin.block_display:
   type: display_variant.plugin
-  label: 'Block display variant'
+  label: 'Block variant plugin'
   mapping:
     selection_logic:
       type: string
@@ -88,7 +124,7 @@ display_variant.plugin.block_display:
 
 display_variant.plugin.http_status_code:
   type: display_variant.plugin
-  label: 'HTTP status code display variant'
+  label: 'HTTP status code variant plugin'
   mapping:
     status_code:
       type: integer
diff --git a/page_manager.routing.yml b/page_manager.routing.yml
index 01432bf..5137742 100644
--- a/page_manager.routing.yml
+++ b/page_manager.routing.yml
@@ -108,102 +108,102 @@ page_manager.static_context_delete:
   requirements:
     _entity_access: page.update
 
-#### Display variants
+#### Variants
 
-page_manager.display_variant_select:
+page_manager.variant_select:
   path: '/admin/structure/page_manager/manage/{page}/add'
   defaults:
-    _controller: '\Drupal\page_manager\Controller\PageManagerController::selectDisplayVariant'
-    _title: 'Select display variant'
+    _controller: '\Drupal\page_manager\Controller\PageManagerController::selectVariant'
+    _title: 'Select variant'
   requirements:
     _entity_access: page.update
 
-page_manager.display_variant_add:
-  path: '/admin/structure/page_manager/manage/{page}/add/{display_variant_id}'
+entity.page_variant.add_form:
+  path: '/admin/structure/page_manager/manage/{page}/add/{variant_plugin_id}'
   defaults:
-    _form: '\Drupal\page_manager\Form\DisplayVariantAddForm'
-    _title: 'Add new display variant'
+    _controller: '\Drupal\page_manager\Controller\PageManagerController::addPageVariantEntityForm'
+    _title: 'Add page variant'
   requirements:
-    _entity_access: page.update
+    _entity_create_access: page_variant
 
-page_manager.display_variant_edit:
-  path: '/admin/structure/page_manager/manage/{page}/manage/{display_variant_id}'
+entity.page_variant.edit_form:
+  path: '/admin/structure/page_manager/variant/{page_variant}'
   defaults:
-    _form: '\Drupal\page_manager\Form\DisplayVariantEditForm'
-    _title_callback: '\Drupal\page_manager\Controller\PageManagerController::editDisplayVariantTitle'
+    _entity_form: 'page_variant.edit'
+    _title_callback: '\Drupal\page_manager\Controller\PageManagerController::editPageVariantTitle'
   requirements:
-    _entity_access: page.update
+    _entity_access: page_variant.update
 
-page_manager.display_variant_delete:
-  path: '/admin/structure/page_manager/manage/{page}/manage/{display_variant_id}/delete'
+entity.page_variant.delete_form:
+  path: '/admin/structure/page_variant/variant/{page_variant}/delete'
   defaults:
-    _form: '\Drupal\page_manager\Form\DisplayVariantDeleteForm'
-    _title: 'Delete display variant'
+    _entity_form: 'page_variant.delete'
+    _title: 'Delete page variant'
   requirements:
-    _entity_access: page.update
+    _entity_access: page_variant.delete
 
-page_manager.display_variant_select_block:
-  path: '/admin/structure/page_manager/manage/{page}/manage/{display_variant_id}/block/select'
+page_manager.variant_select_block:
+  path: '/admin/structure/page_manager/variant/{page_variant}/block/select'
   defaults:
     _controller: '\Drupal\page_manager\Controller\PageManagerController::selectBlock'
     _title: 'Select block'
   requirements:
-    _entity_access: page.update
+    _entity_access: page_variant.update
 
-page_manager.display_variant_add_block:
-  path: '/admin/structure/page_manager/manage/{page}/manage/{display_variant_id}/block/add/{block_id}'
+page_manager.variant_add_block:
+  path: '/admin/structure/page_manager/variant/{page_variant}/block/add/{block_id}'
   defaults:
-    _form: '\Drupal\page_manager\Form\DisplayVariantAddBlockForm'
-    _title: 'Add block to display variant'
+    _form: '\Drupal\page_manager\Form\VariantPluginAddBlockForm'
+    _title: 'Add block to variant'
   requirements:
-    _entity_access: page.update
+    _entity_access: page_variant.update
 
-page_manager.display_variant_edit_block:
-  path: '/admin/structure/page_manager/manage/{page}/manage/{display_variant_id}/block/edit/{block_id}'
+page_manager.variant_edit_block:
+  path: '/admin/structure/page_manager/variant/{page_variant}/block/edit/{block_id}'
   defaults:
-    _form: '\Drupal\page_manager\Form\DisplayVariantEditBlockForm'
-    _title: 'Edit block in display variant'
+    _form: '\Drupal\page_manager\Form\VariantPluginEditBlockForm'
+    _title: 'Edit block in variant'
   requirements:
-    _entity_access: page.update
+    _entity_access: page_variant.update
 
-page_manager.display_variant_delete_block:
-  path: '/admin/structure/page_manager/manage/{page}/manage/{display_variant_id}/block/delete/{block_id}'
+page_manager.variant_delete_block:
+  path: '/admin/structure/page_manager/variant/{page_variant}/block/delete/{block_id}'
   defaults:
-    _form: '\Drupal\page_manager\Form\DisplayVariantDeleteBlockForm'
-    _title: 'Delete block in display variant'
+    _form: '\Drupal\page_manager\Form\VariantPluginDeleteBlockForm'
+    _title: 'Delete block in variant'
   requirements:
-    _entity_access: page.update
+    _entity_access: page_variant.update
 
 #### Selection Conditions
 
 page_manager.selection_condition_select:
-  path: '/admin/structure/page_manager/manage/{page}/manage/{display_variant_id}/selection/select'
+  path: '/admin/structure/page_manager/variant/{page_variant}/selection/select'
   defaults:
     _controller: '\Drupal\page_manager\Controller\PageManagerController::selectSelectionCondition'
     _title: 'Select selection condition'
   requirements:
-    _entity_access: page.update
+    _entity_access: page_variant.update
 
 page_manager.selection_condition_add:
-  path: '/admin/structure/page_manager/manage/{page}/manage/{display_variant_id}/selection/add/{condition_id}'
+  path: '/admin/structure/page_manager/variant/{page_variant}/selection/add/{condition_id}'
   defaults:
     _form: '\Drupal\page_manager\Form\SelectionConditionAddForm'
     _title: 'Add new selection condition'
   requirements:
-    _entity_access: page.update
+    _entity_access: page_variant.update
 
 page_manager.selection_condition_edit:
-  path: '/admin/structure/page_manager/manage/{page}/manage/{display_variant_id}/selection/edit/{condition_id}'
+  path: '/admin/structure/page_manager/variant/{page_variant}/selection/edit/{condition_id}'
   defaults:
     _form: '\Drupal\page_manager\Form\SelectionConditionEditForm'
     _title_callback: '\Drupal\page_manager\Controller\PageManagerController::editSelectionConditionTitle'
   requirements:
-    _entity_access: page.update
+    _entity_access: page_variant.update
 
 page_manager.selection_condition_delete:
-  path: '/admin/structure/page_manager/manage/{page}/manage/{display_variant_id}/selection/delete/{condition_id}'
+  path: '/admin/structure/page_manager/variant/{page_variant}/selection/delete/{condition_id}'
   defaults:
     _form: '\Drupal\page_manager\Form\SelectionConditionDeleteForm'
     _title: 'Delete selection condition'
   requirements:
-    _entity_access: page.update
+    _entity_access: page_variant.update
diff --git a/src/Controller/PageManagerController.php b/src/Controller/PageManagerController.php
index a8f04a0..7599940 100644
--- a/src/Controller/PageManagerController.php
+++ b/src/Controller/PageManagerController.php
@@ -14,6 +14,7 @@
 use Drupal\Core\Url;
 use Drupal\ctools\Form\AjaxFormTrait;
 use Drupal\page_manager\PageInterface;
+use Drupal\page_manager\PageVariantInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\HttpFoundation\Request;
 
@@ -53,7 +54,7 @@ class PageManagerController extends ControllerBase {
   protected $contextHandler;
 
   /**
-   * Constructs a new DisplayVariantEditForm.
+   * Constructs a new VariantPluginEditForm.
    *
    * @param \Drupal\Core\Block\BlockManagerInterface $block_manager
    *   The block manager.
@@ -99,17 +100,14 @@ public function editPageTitle(PageInterface $page) {
   /**
    * Route title callback.
    *
-   * @param \Drupal\page_manager\PageInterface $page
-   *   The page entity.
-   * @param string $display_variant_id
-   *   The display variant ID.
+   * @param \Drupal\page_manager\PageVariantInterface $page_variant
+   *   The page variant entity.
    *
    * @return string
-   *   The title for the display variant edit form.
+   *   The title for the page variant edit form.
    */
-  public function editDisplayVariantTitle(PageInterface $page, $display_variant_id) {
-    $display_variant = $page->getVariant($display_variant_id);
-    return $this->t('Edit %label display variant', ['%label' => $display_variant->label()]);
+  public function editPageVariantTitle(PageVariantInterface $page_variant) {
+    return $this->t('Edit %label variant', ['%label' => $page_variant->label()]);
   }
 
   /**
@@ -131,20 +129,16 @@ public function editAccessConditionTitle(PageInterface $page, $condition_id) {
   /**
    * Route title callback.
    *
-   * @param \Drupal\page_manager\PageInterface $page
-   *   The page entity.
-   * @param string $display_variant_id
-   *   The display variant ID.
+   * @param \Drupal\page_manager\PageVariantInterface $page_variant
+   *   The page variant entity.
    * @param string $condition_id
    *   The selection condition ID.
    *
    * @return string
    *   The title for the selection condition edit form.
    */
-  public function editSelectionConditionTitle(PageInterface $page, $display_variant_id, $condition_id) {
-    /** @var \Drupal\ctools\Plugin\ConditionVariantInterface $display_variant */
-    $display_variant = $page->getVariant($display_variant_id);
-    $selection_condition = $display_variant->getSelectionCondition($condition_id);
+  public function editSelectionConditionTitle(PageVariantInterface $page_variant, $condition_id) {
+    $selection_condition = $page_variant->getSelectionCondition($condition_id);
     return $this->t('Edit %label selection condition', ['%label' => $selection_condition->getPluginDefinition()['label']]);
   }
 
@@ -189,25 +183,25 @@ public function performPageOperation(PageInterface $page, $op) {
   }
 
   /**
-   * Presents a list of display variants to add to the page entity.
+   * Presents a list of variants to add to the page entity.
    *
    * @param \Drupal\page_manager\PageInterface $page
    *   The page entity.
    *
    * @return array
-   *   The display variant selection page.
+   *   The variant selection page.
    */
-  public function selectDisplayVariant(PageInterface $page) {
+  public function selectVariant(PageInterface $page) {
     $build = [
       '#theme' => 'links',
       '#links' => [],
     ];
-    foreach ($this->variantManager->getDefinitions() as $display_variant_id => $display_variant) {
-      $build['#links'][$display_variant_id] = [
-        'title' => $display_variant['admin_label'],
-        'url' => Url::fromRoute('page_manager.display_variant_add', [
+    foreach ($this->variantManager->getDefinitions() as $variant_plugin_id => $variant_plugin) {
+      $build['#links'][$variant_plugin_id] = [
+        'title' => $variant_plugin['admin_label'],
+        'url' => Url::fromRoute('entity.page_variant.add_form', [
           'page' => $page->id(),
-          'display_variant_id' => $display_variant_id,
+          'variant_plugin_id' => $variant_plugin_id,
         ]),
         'attributes' => $this->getAjaxAttributes(),
       ];
@@ -246,26 +240,23 @@ public function selectAccessCondition(PageInterface $page) {
   /**
    * Presents a list of selection conditions to add to the page entity.
    *
-   * @param \Drupal\page_manager\PageInterface $page
-   *   The page entity.
-   * @param string $display_variant_id
-   *   The display variant ID.
+   * @param \Drupal\page_manager\PageVariantInterface $page_variant
+   *   The page variant entity.
    *
    * @return array
    *   The selection condition selection page.
    */
-  public function selectSelectionCondition(PageInterface $page, $display_variant_id) {
+  public function selectSelectionCondition(PageVariantInterface $page_variant) {
     $build = [
       '#theme' => 'links',
       '#links' => [],
     ];
-    $available_plugins = $this->conditionManager->getDefinitionsForContexts($page->getContexts());
+    $available_plugins = $this->conditionManager->getDefinitionsForContexts($page_variant->getContexts());
     foreach ($available_plugins as $selection_id => $selection_condition) {
       $build['#links'][$selection_id] = [
         'title' => $selection_condition['label'],
         'url' => Url::fromRoute('page_manager.selection_condition_add', [
-          'page' => $page->id(),
-          'display_variant_id' => $display_variant_id,
+          'page_variant' => $page_variant->id(),
           'condition_id' => $selection_id,
         ]),
         'attributes' => $this->getAjaxAttributes(),
@@ -275,19 +266,17 @@ public function selectSelectionCondition(PageInterface $page, $display_variant_i
   }
 
   /**
-   * Presents a list of blocks to add to the display variant.
+   * Presents a list of blocks to add to the variant.
    *
    * @param \Symfony\Component\HttpFoundation\Request $request
    *   The current request.
-   * @param \Drupal\page_manager\PageInterface $page
+   * @param \Drupal\page_manager\PageVariantInterface $page_variant
    *   The page entity.
-   * @param string $display_variant_id
-   *   The display variant ID.
    *
    * @return array
    *   The block selection page.
    */
-  public function selectBlock(Request $request, PageInterface $page, $display_variant_id) {
+  public function selectBlock(Request $request, PageVariantInterface $page_variant) {
     // Add a section containing the available blocks to be added to the variant.
     $build = [
       '#type' => 'container',
@@ -297,7 +286,7 @@ public function selectBlock(Request $request, PageInterface $page, $display_vari
         ],
       ],
     ];
-    $available_plugins = $this->blockManager->getDefinitionsForContexts($page->getContexts());
+    $available_plugins = $this->blockManager->getDefinitionsForContexts($page_variant->getContexts());
     foreach ($available_plugins as $plugin_id => $plugin_definition) {
       // Make a section for each region.
       $category = $plugin_definition['category'];
@@ -314,9 +303,8 @@ public function selectBlock(Request $request, PageInterface $page, $display_vari
       // 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.display_variant_add_block', [
-          'page' => $page->id(),
-          'display_variant_id' => $display_variant_id,
+        'url' => Url::fromRoute('page_manager.variant_add_block', [
+          'page_variant' => $page_variant->id(),
           'block_id' => $plugin_id,
           'region' => $request->query->get('region'),
         ]),
@@ -326,4 +314,22 @@ public function selectBlock(Request $request, PageInterface $page, $display_vari
     return $build;
   }
 
+  /**
+   * Build the page variant entity add form.
+   *
+   * @param \Drupal\page_manager\PageInterface $page
+   *   The page this page variant belongs to.
+   * @param string $variant_plugin_id
+   *   The variant plugin ID.
+   *
+   * @return array
+   *   The page variant entity add form.
+   */
+  public function addPageVariantEntityForm(PageInterface $page, $variant_plugin_id) {
+    // Create a page variant entity.
+    $entity = $this->entityManager()->getStorage('page_variant')->create(['page' => $page->id(), 'variant' => $variant_plugin_id]);
+
+    return $this->entityFormBuilder()->getForm($entity, 'add');
+  }
+
 }
diff --git a/src/Entity/Page.php b/src/Entity/Page.php
index 20815a8..4b0d038 100644
--- a/src/Entity/Page.php
+++ b/src/Entity/Page.php
@@ -11,7 +11,7 @@
 use Drupal\Core\Condition\ConditionPluginCollection;
 use Drupal\Core\Config\Entity\ConfigEntityBase;
 use Drupal\Core\Entity\EntityStorageInterface;
-use Drupal\page_manager\Plugin\VariantAwareTrait;
+use Drupal\page_manager\PageVariantInterface;
 
 /**
  * Defines a Page entity class.
@@ -40,7 +40,6 @@
  *     "label",
  *     "use_admin_theme",
  *     "path",
- *     "display_variants",
  *     "access_logic",
  *     "access_conditions",
  *   },
@@ -56,8 +55,6 @@
  */
 class Page extends ConfigEntityBase implements PageInterface {
 
-  use VariantAwareTrait;
-
   /**
    * The ID of the page entity.
    *
@@ -80,11 +77,11 @@ class Page extends ConfigEntityBase implements PageInterface {
   protected $path;
 
   /**
-   * The configuration of the display variants.
+   * The page variant entities.
    *
-   * @var array
+   * @var \Drupal\page_manager\PageVariantInterface[].
    */
-  protected $display_variants = [];
+  protected $displayVariants;
 
   /**
    * The configuration of access conditions.
@@ -160,7 +157,6 @@ public function toArray() {
       'id',
       'label',
       'path',
-      'display_variants',
       'access_conditions',
       'access_logic',
       'use_admin_theme',
@@ -191,14 +187,6 @@ public function usesAdminTheme() {
    */
   public function postCreate(EntityStorageInterface $storage) {
     parent::postCreate($storage);
-    // Ensure there is at least one display variant.
-    if (!count($this->getVariants())) {
-      $this->addVariant([
-        'id' => 'http_status_code',
-        'label' => 'Default',
-        'weight' => 10,
-      ]);
-    }
   }
 
   /**
@@ -238,10 +226,19 @@ protected function configFactory() {
   }
 
   /**
+   * Wraps the entityManager getStorage for page variants.
+   *
+   * @return \Drupal\Core\Entity\EntityStorageInterface
+   */
+  protected function entityStorage() {
+    return \Drupal::entityManager()->getStorage('page_variant');
+  }
+
+  /**
    * {@inheritdoc}
    */
   protected function getVariantConfig() {
-    return $this->get('display_variants');
+    return $this->displayVariants;
   }
 
   /**
@@ -249,7 +246,6 @@ protected function getVariantConfig() {
    */
   public function getPluginCollections() {
     return [
-      'display_variants' => $this->getVariants(),
       'access_conditions' => $this->getAccessConditions(),
     ];
   }
@@ -346,13 +342,84 @@ public function isFallbackPage() {
   /**
    * {@inheritdoc}
    */
+  public function addVariant(array $configuration) {
+    if (empty($configuration['id'])) {
+      throw new \Exception("Must pass an 'id' for the new page variant.");
+    }
+    if (strpos($configuration['id'], '.') === FALSE) {
+      $configuration['id'] = $this->id() . '.' . $configuration['id'];
+    }
+    $configuration['page'] = $this->id();
+    /** @var \Drupal\page_manager\PageVariantInterface $variant */
+    $variant = $this->entityStorage()->create($configuration);
+    return $this->addVariantEntity($variant);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function addVariantEntity(PageVariantInterface $variant) {
+    $this->displayVariants[$variant->id()] = $variant;
+    return $variant->id();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getVariant($variant_id) {
+    $variants = $this->getVariants();
+    if (!isset($variants[$variant_id])) {
+      throw new \Exception('The requested variant does not exist or is not associated with this page.');
+    }
+    return $variants[$variant_id];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function removeVariant($variant_id) {
+    $this->getVariant($variant_id)->delete();
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getVariants() {
+    if (!isset($this->displayVariants)) {
+      $this->displayVariants = [];
+      /** @var \Drupal\page_manager\PageVariantInterface $variant */
+      foreach ($this->entityStorage()->loadByProperties(['page' => $this->id()]) as $variant) {
+        $this->displayVariants[$variant->uuid()] = $variant;
+      }
+      uasort($this->displayVariants, [$this, 'variantSortHelper']);
+    }
+    return $this->displayVariants;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function variantSortHelper($a, $b) {
+    $a_weight = $a->getWeight();
+    $b_weight = $b->getWeight();
+    if ($a_weight == $b_weight) {
+      return 0;
+    }
+
+    return ($a_weight < $b_weight) ? -1 : 1;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   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 = [
-      'variantCollection' => 'display_variants',
+      'displayVariants' => NULL,
       'accessConditionCollection' => 'access_variants',
       'executable' => NULL,
     ];
diff --git a/src/Entity/PageVariant.php b/src/Entity/PageVariant.php
new file mode 100644
index 0000000..7ebc839
--- /dev/null
+++ b/src/Entity/PageVariant.php
@@ -0,0 +1,360 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\page_manager\Entity\PageVariant.
+ */
+
+namespace Drupal\page_manager\Entity;
+
+use Drupal\Core\Cache\Cache;
+use Drupal\Core\Condition\ConditionAccessResolverTrait;
+use Drupal\Core\Condition\ConditionPluginCollection;
+use Drupal\Core\Config\Entity\ConfigEntityBase;
+use Drupal\Core\Display\ContextAwareVariantInterface;
+use Drupal\Core\Entity\EntityStorageInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Plugin\ContextAwarePluginInterface;
+use Drupal\Core\Plugin\DefaultSingleLazyPluginCollection;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\page_manager\PageVariantInterface;
+
+/**
+ * Defines the page variant entity.
+ *
+ * @ConfigEntityType(
+ *   id = "page_variant",
+ *   label = @Translation("Variant"),
+ *   handlers = {
+ *     "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 = {
+ *     "id" = "id",
+ *     "label" = "label",
+ *     "uuid" = "uuid"
+ *   },
+ *   config_export = {
+ *     "id",
+ *     "label",
+ *     "uuid",
+ *     "variant",
+ *     "variant_settings",
+ *     "page",
+ *     "weight",
+ *     "selection_criteria",
+ *     "selection_logic",
+ *     "contexts"
+ *   },
+ *   links = {
+ *     "edit-form" = "/admin/structure/page_manager/variant/{page_variant}",
+ *     "delete-form" = "/admin/structure/page_manager/variant/{page_variant}/delete",
+ *   },
+ *   lookup_keys = {
+ *     "page"
+ *   }
+ * )
+ */
+class PageVariant extends ConfigEntityBase implements PageVariantInterface {
+
+  use ConditionAccessResolverTrait;
+
+  /**
+   * The ID of the page variant entity.
+   *
+   * @var string
+   */
+  protected $id;
+
+  /**
+   * The label of the page variant entity.
+   *
+   * @var string
+   */
+  protected $label;
+
+  /**
+   * The weight of the page variant entity.
+   *
+   * @var int
+   */
+  protected $weight = 0;
+
+  /**
+   * The UUID of the page variant entity.
+   *
+   * @var string
+   */
+  protected $uuid;
+
+  /**
+   * The ID of the variant plugin.
+   *
+   * @var string
+   */
+  protected $variant;
+
+  /**
+   * The plugin configuration for the variant plugin.
+   *
+   * @var array
+   */
+  protected $variant_settings = [];
+
+  /**
+   * The ID of the page entity this page variant entity belongs to.
+   *
+   * @var string
+   */
+  protected $page;
+
+  /**
+   * The plugin configuration for the selection criteria condition plugins.
+   *
+   * @var array
+   */
+  protected $selection_criteria = [];
+
+  /**
+   * The selection logic for this page variant entity (either 'and' or 'or').
+   *
+   * @var string
+   */
+  protected $selection_logic = 'and';
+
+  /**
+   * @var array
+   */
+  protected $contexts = [];
+
+  /**
+   * The plugin collection that holds the selection criteria condition plugins.
+   *
+   * @var \Drupal\Component\Plugin\LazyPluginCollection
+   */
+  protected $selectionConditionCollection;
+
+  /**
+   * The plugin collection that holds the single variant plugin instance.
+   *
+   * @var \Drupal\Core\Plugin\DefaultSingleLazyPluginCollection
+   */
+  protected $variantPluginCollection;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function invalidateTagsOnSave($update) {
+    parent::invalidateTagsOnSave($update);
+
+    // The parent doesn't invalidate the entity cache tags on save because the
+    // config system will invalidate them, but since we're using the parent
+    // page's cache tags, we need to invalidate them special.
+    Cache::invalidateTags($this->getCacheTagsToInvalidate());
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static function invalidateTagsOnDelete(EntityTypeInterface $entity_type, array $entities) {
+    parent::invalidateTagsOnDelete($entity_type, $entities);
+
+    // The parent doesn't invalidate the entity cache tags on delete because the
+    // config system will invalidate them, but since we're using the parent
+    // page's cache tags, we need to invalidate them special.
+    $tags = [];
+    foreach ($entities as $entity) {
+      $tags = Cache::mergeTags($tags, $entity->getCacheTagsToInvalidate());
+    }
+    Cache::invalidateTags($tags);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheTagsToInvalidate() {
+    // We use the same cache tags as the parent page.
+    return $this->getPage()->getCacheTagsToInvalidate();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function calculateDependencies() {
+    parent::calculateDependencies();
+
+    $this->addDependency('config', $this->getPage()->getConfigDependencyName());
+
+    foreach ($this->getSelectionConditions() as $instance) {
+      $this->calculatePluginDependencies($instance);
+    }
+
+    return $this->getDependencies();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getPluginCollections() {
+    return [
+      'selection_criteria' => $this->getSelectionConditions(),
+      'variant_settings' => $this->getVariantPluginCollection(),
+    ];
+  }
+
+  /**
+   * Get the plugin collection that holds the single variant plugin instance.
+   *
+   * @return \Drupal\Core\Plugin\DefaultSingleLazyPluginCollection
+   *   The plugin collection that holds the single variant plugin instance.
+   */
+  protected function getVariantPluginCollection() {
+    if (!$this->variantPluginCollection) {
+      $this->variantPluginCollection = new DefaultSingleLazyPluginCollection(\Drupal::service('plugin.manager.display_variant'), $this->variant, $this->variant_settings);
+    }
+    return $this->variantPluginCollection;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getVariantPlugin() {
+    return $this->getVariantPluginCollection()->get($this->variant);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getVariantPluginId() {
+    return $this->variant;
+  }
+
+  /**
+   * Gets the page this variant is on.
+   *
+   * @return \Drupal\page_manager\Entity\Page
+   */
+  protected function getPage() {
+    return Page::load($this->page);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getContexts() {
+    return array_merge($this->getPage()->getContexts(), $this->contexts);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getWeight() {
+    return $this->weight;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setWeight($weight) {
+    $this->weight = $weight;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getSelectionConditions() {
+    if (!$this->selectionConditionCollection) {
+      $this->selectionConditionCollection = new ConditionPluginCollection(\Drupal::service('plugin.manager.condition'), $this->get('selection_criteria'));
+    }
+    return $this->selectionConditionCollection;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function addSelectionCondition(array $configuration) {
+    $configuration['uuid'] = $this->uuidGenerator()->generate();
+    $this->getSelectionConditions()->addInstanceId($configuration['uuid'], $configuration);
+    return $configuration['uuid'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getSelectionCondition($condition_id) {
+    return $this->getSelectionConditions()->get($condition_id);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function removeSelectionCondition($condition_id) {
+    $this->getSelectionConditions()->removeInstanceId($condition_id);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getSelectionLogic() {
+    return $this->selection_logic;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function canSelect(AccountInterface $account = NULL) {
+    $conditions = $this->getSelectionConditions();
+    $contexts = $this->getContexts();
+
+    foreach ($conditions as $condition) {
+      if ($condition instanceof ContextAwarePluginInterface) {
+        /** @var \Drupal\Core\Plugin\Context\ContextHandlerInterface $context_handler */
+        $context_handler = \Drupal::service('context.handler');
+        $context_handler->applyContextMapping($condition, $contexts);
+      }
+    }
+
+    if ($this->resolveConditions($conditions, $this->getSelectionLogic())) {
+      $variant_plugin = $this->getVariantPlugin();
+      if ($variant_plugin instanceof ContextAwareVariantInterface) {
+        $variant_plugin->setContexts($this->getContexts());
+      }
+      return $variant_plugin->access($account);
+    }
+
+    return FALSE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function postSave(EntityStorageInterface $storage, $update = TRUE) {
+    parent::postSave($storage, $update);
+    static::routeBuilder()->setRebuildNeeded();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function postDelete(EntityStorageInterface $storage, array $entities) {
+    parent::postDelete($storage, $entities);
+    static::routeBuilder()->setRebuildNeeded();
+  }
+
+  /**
+   * Wraps the route builder.
+   *
+   * @return \Drupal\Core\Routing\RouteBuilderInterface
+   *   An object for state storage.
+   */
+  protected static function routeBuilder() {
+    return \Drupal::service('router.builder');
+  }
+
+}
diff --git a/src/Entity/PageViewBuilder.php b/src/Entity/PageViewBuilder.php
index ede37a8..75c1082 100644
--- a/src/Entity/PageViewBuilder.php
+++ b/src/Entity/PageViewBuilder.php
@@ -12,6 +12,7 @@
 use Drupal\Core\Entity\EntityManagerInterface;
 use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Entity\EntityViewBuilder;
+use Symfony\Component\HttpKernel\Exception\HttpException;
 use Drupal\Core\Language\LanguageManagerInterface;
 use Drupal\Core\Routing\RouteMatchInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -61,15 +62,18 @@ public static function createInstance(ContainerInterface $container, EntityTypeI
    * {@inheritdoc}
    */
   public function view(EntityInterface $entity, $view_mode = 'full', $langcode = NULL) {
-    $build = [];
     /** @var \Drupal\page_manager\PageInterface $entity */
-    if ($display_variant = $entity->getExecutable()->getRuntimeVariant($this->routeMatch->getParameter('variant_id'))) {
-      if ($display_variant instanceof RefinableCacheableDependencyInterface) {
-        $display_variant->addCacheableDependency($entity);
+    if ($variant_entity = $entity->getExecutable()->getRuntimeVariant($this->routeMatch->getParameter('variant_id'))) {
+      $variant_plugin = $variant_entity->getVariantPlugin();
+
+      if ($variant_plugin instanceof RefinableCacheableDependencyInterface) {
+        $variant_plugin->addCacheableDependency($entity);
       }
-      $build = $display_variant->build();
+      return $variant_plugin->build();
+    }
+    else {
+      throw new HttpException(404);
     }
-    return $build;
   }
 
   /**
diff --git a/src/Form/AccessConditionFormBase.php b/src/Form/AccessConditionFormBase.php
index a0dc108..f50ac9f 100644
--- a/src/Form/AccessConditionFormBase.php
+++ b/src/Form/AccessConditionFormBase.php
@@ -8,6 +8,7 @@
 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.
@@ -15,6 +16,21 @@
 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) {
diff --git a/src/Form/ConditionFormBase.php b/src/Form/ConditionFormBase.php
index 7ba10f7..4d0528e 100644
--- a/src/Form/ConditionFormBase.php
+++ b/src/Form/ConditionFormBase.php
@@ -12,7 +12,6 @@
 use Drupal\Core\Form\FormBase;
 use Drupal\Core\Plugin\ContextAwarePluginInterface;
 use Drupal\Core\Plugin\ContextAwarePluginAssignmentTrait;
-use Drupal\page_manager\PageInterface;
 
 /**
  * Provides a base form for editing and adding a condition.
@@ -22,13 +21,6 @@
   use ContextAwarePluginAssignmentTrait;
 
   /**
-   * The page entity this condition belongs to.
-   *
-   * @var \Drupal\page_manager\PageInterface
-   */
-  protected $page;
-
-  /**
    * The condition used by this form.
    *
    * @var \Drupal\Core\Condition\ConditionInterface
@@ -66,11 +58,10 @@
   /**
    * {@inheritdoc}
    */
-  public function buildForm(array $form, FormStateInterface $form_state, PageInterface $page = NULL, $condition_id = NULL) {
-    $this->page = $page;
+  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'] = $this->page->getContexts();
+    $temporary['gathered_contexts'] = $contexts;
     $form_state->setTemporary($temporary);
 
     // Allow the condition to add to the form.
diff --git a/src/Form/DisplayVariantAddForm.php b/src/Form/DisplayVariantAddForm.php
deleted file mode 100644
index 334c48b..0000000
--- a/src/Form/DisplayVariantAddForm.php
+++ /dev/null
@@ -1,85 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\page_manager\Form\DisplayVariantAddForm.
- */
-
-namespace Drupal\page_manager\Form;
-
-use Drupal\Core\Display\VariantManager;
-use Drupal\Core\Form\FormStateInterface;
-use Symfony\Component\DependencyInjection\ContainerInterface;
-
-/**
- * Provides a form for adding a new display variant.
- */
-class DisplayVariantAddForm extends DisplayVariantFormBase {
-
-  /**
-   * The variant manager.
-   *
-   * @var \Drupal\Core\Display\VariantManager
-   */
-  protected $variantManager;
-
-  /**
-   * Constructs a new DisplayVariantAddForm.
-   *
-   * @param \Drupal\Core\Display\VariantManager $variant_manager
-   *   The variant manager.
-   */
-  public function __construct(VariantManager $variant_manager) {
-    $this->variantManager = $variant_manager;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function create(ContainerInterface $container) {
-    return new static(
-      $container->get('plugin.manager.display_variant')
-    );
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getFormId() {
-    return 'page_manager_display_variant_add_form';
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function submitText() {
-    return $this->t('Add display variant');
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function submitForm(array &$form, FormStateInterface $form_state) {
-    parent::submitForm($form, $form_state);
-
-    // If this display variant is new, add it to the page.
-    $display_variant_id = $this->page->addVariant($this->displayVariant->getConfiguration());
-
-    // Save the page entity.
-    $this->page->save();
-    drupal_set_message($this->t('The %label display variant has been added.', ['%label' => $this->displayVariant->label()]));
-    $form_state->setRedirect('page_manager.display_variant_edit', [
-      'page' => $this->page->id(),
-      'display_variant_id' => $display_variant_id,
-    ]);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function prepareDisplayVariant($display_variant_id) {
-    // Create a new display variant instance.
-    return $this->variantManager->createInstance($display_variant_id);
-  }
-
-}
diff --git a/src/Form/DisplayVariantDeleteForm.php b/src/Form/DisplayVariantDeleteForm.php
deleted file mode 100644
index e9552d2..0000000
--- a/src/Form/DisplayVariantDeleteForm.php
+++ /dev/null
@@ -1,80 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\page_manager\Form\DisplayVariantDeleteForm.
- */
-
-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 a display variant.
- */
-class DisplayVariantDeleteForm extends ConfirmFormBase {
-
-  /**
-   * The page entity this display variant belongs to.
-   *
-   * @var \Drupal\page_manager\PageInterface
-   */
-  protected $page;
-
-  /**
-   * The display variant.
-   *
-   * @var \Drupal\Core\Display\VariantInterface
-   */
-  protected $displayVariant;
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getFormId() {
-    return 'page_manager_display_variant_delete_form';
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getQuestion() {
-    return $this->t('Are you sure you want to delete the display variant %name?', ['%name' => $this->displayVariant->label()]);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getCancelUrl() {
-    return $this->page->urlInfo('edit-form');
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getConfirmText() {
-    return $this->t('Delete');
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function buildForm(array $form, FormStateInterface $form_state, PageInterface $page = NULL, $display_variant_id = NULL) {
-    $this->page = $page;
-    $this->displayVariant = $page->getVariant($display_variant_id);
-    return parent::buildForm($form, $form_state);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function submitForm(array &$form, FormStateInterface $form_state) {
-    $this->page->removeVariant($this->displayVariant->id());
-    $this->page->save();
-    drupal_set_message($this->t('The display variant %name has been removed.', ['%name' => $this->displayVariant->label()]));
-    $form_state->setRedirectUrl($this->getCancelUrl());
-  }
-
-}
diff --git a/src/Form/DisplayVariantEditForm.php b/src/Form/DisplayVariantEditForm.php
deleted file mode 100644
index be5f26a..0000000
--- a/src/Form/DisplayVariantEditForm.php
+++ /dev/null
@@ -1,334 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\page_manager\Form\DisplayVariantEditForm.
- */
-
-namespace Drupal\page_manager\Form;
-
-use Drupal\Core\Form\FormStateInterface;
-use Drupal\Core\Url;
-use Drupal\ctools\Form\AjaxFormTrait;
-use Drupal\ctools\Plugin\BlockVariantInterface;
-use Drupal\ctools\Plugin\ConditionVariantInterface;
-use Drupal\page_manager\PageInterface;
-
-/**
- * Provides a form for editing a display variant.
- */
-class DisplayVariantEditForm extends DisplayVariantFormBase {
-
-  use AjaxFormTrait;
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getFormId() {
-    return 'page_manager_display_variant_edit_form';
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function submitText() {
-    return $this->t('Update display variant');
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function buildForm(array $form, FormStateInterface $form_state, PageInterface $page = NULL, $display_variant_id = NULL) {
-    $form = parent::buildForm($form, $form_state, $page, $display_variant_id);
-
-    if ($this->displayVariant instanceof BlockVariantInterface) {
-      $form['block_section'] = $this->buildBlockForm();
-    }
-    if ($this->displayVariant instanceof ConditionVariantInterface) {
-      $form['selection_section'] = $this->buildSelectionForm();
-    }
-
-    return $form;
-  }
-
-  /**
-   * Builds the block form for a variant.
-   *
-   * @return array
-   */
-  protected function buildBlockForm() {
-    if (!$this->displayVariant instanceof BlockVariantInterface) {
-      return [];
-    }
-
-    // Determine the page ID, used for links below.
-    $page_id = $this->getPage()->id();
-
-    // Set up the attributes used by a modal to prevent duplication later.
-    $attributes = $this->getAjaxAttributes();
-    $add_button_attributes = $this->getAjaxButtonAttributes();
-
-    $form = [];
-    if ($block_assignments = $this->displayVariant->getRegionAssignments()) {
-      // Build a table of all blocks used by this display 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.display_variant_select_block', [
-          'page' => $page_id,
-          'display_variant_id' => $this->displayVariant->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' => ['display_variant', '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' => $this->displayVariant->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' => $this->displayVariant->getRegionNames(),
-            '#default_value' => $this->displayVariant->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.display_variant_edit_block', [
-              'page' => $page_id,
-              'display_variant_id' => $this->displayVariant->id(),
-              'block_id' => $block_id,
-            ]),
-            'attributes' => $attributes,
-          ];
-          $operations['delete'] = [
-            'title' => $this->t('Delete'),
-            'url' => Url::fromRoute('page_manager.display_variant_delete_block', [
-              'page' => $page_id,
-              'display_variant_id' => $this->displayVariant->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();
-
-    $form = [];
-    if ($this->displayVariant instanceof ConditionVariantInterface) {
-      if ($selection_conditions = $this->displayVariant->getSelectionConditions()) {
-        // 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' => $this->page->id(),
-            'display_variant_id' => $this->displayVariant->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' => $this->displayVariant->getSelectionLogic(),
-        ];
-
-        $form['selection'] = [
-          '#tree' => TRUE,
-        ];
-        foreach ($selection_conditions 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' => $this->page->id(),
-              'display_variant_id' => $this->displayVariant->id(),
-              'condition_id' => $selection_id,
-            ]),
-            'attributes' => $attributes,
-          ];
-          $operations['delete'] = [
-            'title' => $this->t('Delete'),
-            'url' => Url::fromRoute('page_manager.selection_condition_delete', [
-              'page' => $this->page->id(),
-              'display_variant_id' => $this->displayVariant->id(),
-              'condition_id' => $selection_id,
-            ]),
-            'attributes' => $attributes,
-          ];
-          $row['operations'] = [
-            '#type' => 'operations',
-            '#links' => $operations,
-          ];
-          $form['table'][$selection_id] = $row;
-        }
-      }
-    }
-
-    return $form;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function submitForm(array &$form, FormStateInterface $form_state) {
-    // @todo This feels very wrong.
-    if ($this->displayVariant instanceof BlockVariantInterface) {
-      // If the blocks were rearranged, update their values.
-      if (!$form_state->isValueEmpty(['display_variant', 'blocks'])) {
-        foreach ($form_state->getValue(['display_variant', 'blocks']) as $block_id => $block_values) {
-          $this->displayVariant->updateBlock($block_id, $block_values);
-        }
-      }
-    }
-
-    parent::submitForm($form, $form_state);
-
-    // Save the page entity.
-    $this->page->save();
-    drupal_set_message($this->t('The %label display variant has been updated.', ['%label' => $this->displayVariant->label()]));
-    $form_state->setRedirectUrl($this->page->urlInfo('edit-form'));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function prepareDisplayVariant($display_variant_id) {
-    // Load the display variant directly from the page entity.
-    return $this->page->getVariant($display_variant_id);
-  }
-
-}
diff --git a/src/Form/DisplayVariantFormBase.php b/src/Form/DisplayVariantFormBase.php
deleted file mode 100644
index 4e76ce9..0000000
--- a/src/Form/DisplayVariantFormBase.php
+++ /dev/null
@@ -1,122 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\page_manager\Form\DisplayVariantFormBase.
- */
-
-namespace Drupal\page_manager\Form;
-
-use Drupal\Core\Display\ContextAwareVariantInterface;
-use Drupal\Core\Form\FormState;
-use Drupal\Core\Form\FormStateInterface;
-use Drupal\page_manager\PageInterface;
-use Drupal\Core\Form\FormBase;
-
-/**
- * Provides a base form for editing and adding a display variant.
- */
-abstract class DisplayVariantFormBase extends FormBase implements DisplayVariantFormInterface {
-
-  /**
-   * The page entity this display variant belongs to.
-   *
-   * @var \Drupal\page_manager\PageInterface
-   */
-  protected $page;
-
-  /**
-   * The display variant used by this form.
-   *
-   * @var \Drupal\Core\Display\VariantInterface
-   */
-  protected $displayVariant;
-
-  /**
-   * Prepares the display variant used by this form.
-   *
-   * @param string $display_variant_id
-   *   Either a display variant ID, or the plugin ID used to create a new
-   *   variant.
-   *
-   * @return \Drupal\Core\Display\VariantInterface
-   *   The display variant object.
-   */
-  abstract protected function prepareDisplayVariant($display_variant_id);
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getPage() {
-    return $this->page;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getDisplayVariant() {
-    return $this->displayVariant;
-  }
-
-  /**
-   * Returns the text to use for the submit button.
-   *
-   * @return string
-   *   The submit button text.
-   */
-  abstract protected function submitText();
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getBaseFormId() {
-    return 'page_manager_display_variant_form';
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function buildForm(array $form, FormStateInterface $form_state, PageInterface $page = NULL, $display_variant_id = NULL) {
-    $this->page = $page;
-    $this->displayVariant = $this->prepareDisplayVariant($display_variant_id);
-    if ($this->displayVariant instanceof ContextAwareVariantInterface) {
-      $this->displayVariant->setContexts($this->page->getExecutable()->getContexts());
-    }
-
-    // Allow the display variant to add to the form.
-    $form['display_variant'] = $this->displayVariant->buildConfigurationForm([], $form_state);
-    $form['display_variant']['#tree'] = TRUE;
-
-    $form['actions'] = ['#type' => 'actions'];
-    $form['actions']['submit'] = [
-      '#type' => 'submit',
-      '#value' => $this->submitText(),
-      '#button_type' => 'primary',
-    ];
-
-    return $form;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function validateForm(array &$form, FormStateInterface $form_state) {
-    // Allow the display variant to validate the form.
-    $display_variant_values = (new FormState())->setValues($form_state->getValue('display_variant'));
-    $this->displayVariant->validateConfigurationForm($form, $display_variant_values);
-    // Update the original form values.
-    $form_state->setValue('display_variant', $display_variant_values->getValues());
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function submitForm(array &$form, FormStateInterface $form_state) {
-    // Allow the display variant to submit the form.
-    $display_variant_values = (new FormState())->setValues($form_state->getValue('display_variant'));
-    $this->displayVariant->submitConfigurationForm($form, $display_variant_values);
-    // Update the original form values.
-    $form_state->setValue('display_variant', $display_variant_values->getValues());
-  }
-
-}
diff --git a/src/Form/DisplayVariantFormInterface.php b/src/Form/DisplayVariantFormInterface.php
deleted file mode 100644
index e615c3f..0000000
--- a/src/Form/DisplayVariantFormInterface.php
+++ /dev/null
@@ -1,31 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\page_manager\Form\DisplayVariantFormInterface.
- */
-
-namespace Drupal\page_manager\Form;
-
-use Drupal\Core\Form\BaseFormIdInterface;
-
-/**
- * Provides an interface for forms used for editing or adding a display variant.
- */
-interface DisplayVariantFormInterface extends BaseFormIdInterface {
-
-  /**
-   * Returns the page entity used by the form.
-   *
-   * @return \Drupal\page_manager\PageInterface
-   */
-  public function getPage();
-
-  /**
-   * Returns the display variant used by the form.
-   *
-   * @return \Drupal\Core\Display\VariantInterface
-   */
-  public function getDisplayVariant();
-
-}
diff --git a/src/Form/PageEditForm.php b/src/Form/PageEditForm.php
index 5003bc4..3994ebd 100644
--- a/src/Form/PageEditForm.php
+++ b/src/Form/PageEditForm.php
@@ -103,15 +103,15 @@ public function form(array $form, FormStateInterface $form_state) {
       $form['context']['available_context'][$name] = $row;
     }
 
-    $form['display_variant_section'] = [
+    $form['variant_section'] = [
       '#type' => 'details',
-      '#title' => $this->t('Display variants'),
+      '#title' => $this->t('Variants'),
       '#open' => TRUE,
     ];
-    $form['display_variant_section']['add_new_page'] = [
+    $form['variant_section']['add_new_page'] = [
       '#type' => 'link',
-      '#title' => $this->t('Add new display variant'),
-      '#url' => Url::fromRoute('page_manager.display_variant_select', [
+      '#title' => $this->t('Add new variant'),
+      '#url' => Url::fromRoute('page_manager.variant_select', [
         'page' => $this->entity->id(),
       ]),
       '#attributes' => $add_button_attributes,
@@ -121,7 +121,7 @@ public function form(array $form, FormStateInterface $form_state) {
         ],
       ],
     ];
-    $form['display_variant_section']['display_variants'] = [
+    $form['variant_section']['variants'] = [
       '#type' => 'table',
       '#header' => [
         $this->t('Label'),
@@ -129,50 +129,49 @@ public function form(array $form, FormStateInterface $form_state) {
         $this->t('Weight'),
         $this->t('Operations'),
       ],
-      '#empty' => $this->t('There are no display variants.'),
+      '#empty' => $this->t('There are no variants.'),
       '#tabledrag' => [[
         'action' => 'order',
         'relationship' => 'sibling',
-        'group' => 'display-variant-weight',
+        'group' => 'variant-weight',
       ]],
     ];
-    foreach ($this->entity->getVariants() as $display_variant_id => $display_variant) {
+    /** @var \Drupal\page_manager\PageVariantInterface $page_variant */
+    foreach ($this->entity->getVariants() as $page_variant) {
       $row = [
         '#attributes' => [
           'class' => ['draggable'],
         ],
       ];
-      $row['label']['#markup'] = $display_variant->label();
-      $row['id']['#markup'] = $display_variant->adminLabel();
+      $row['label']['#markup'] = $page_variant->label();
+      $row['id']['#markup'] = $page_variant->getVariantPlugin()->adminLabel();
       $row['weight'] = [
         '#type' => 'weight',
-        '#default_value' => $display_variant->getWeight(),
-        '#title' => $this->t('Weight for @display_variant display variant', ['@display_variant' => $display_variant->label()]),
+        '#default_value' => $page_variant->getWeight(),
+        '#title' => $this->t('Weight for @page_variant variant', ['@page_variant' => $page_variant->label()]),
         '#title_display' => 'invisible',
         '#attributes' => [
-          'class' => ['display-variant-weight'],
+          'class' => ['variant-weight'],
         ],
       ];
       $operations = [];
       $operations['edit'] = [
         'title' => $this->t('Edit'),
-        'url' => Url::fromRoute('page_manager.display_variant_edit', [
-          'page' => $this->entity->id(),
-          'display_variant_id' => $display_variant_id,
+        'url' => Url::fromRoute('entity.page_variant.edit_form', [
+          'page_variant' => $page_variant->id(),
         ]),
       ];
       $operations['delete'] = [
         'title' => $this->t('Delete'),
-        'url' => Url::fromRoute('page_manager.display_variant_delete', [
-          'page' => $this->entity->id(),
-          'display_variant_id' => $display_variant_id,
+        'url' => Url::fromRoute('entity.page_variant.delete_form', [
+          'page_variant' => $page_variant->id(),
         ]),
       ];
       $row['operations'] = [
         '#type' => 'operations',
         '#links' => $operations,
       ];
-      $form['display_variant_section']['display_variants'][$display_variant_id] = $row;
+      $form['variant_section']['variants'][$page_variant->uuid()] = $row;
     }
 
     if ($access_conditions = $this->entity->getAccessConditions()) {
@@ -251,10 +250,11 @@ public function form(array $form, FormStateInterface $form_state) {
    * {@inheritdoc}
    */
   public function save(array $form, FormStateInterface $form_state) {
-    if (!$form_state->isValueEmpty('display_variants')) {
-      foreach ($form_state->getValue('display_variants') as $display_variant_id => $data) {
-        if ($display_variant = $this->entity->getVariant($display_variant_id)) {
-          $display_variant->setWeight($data['weight']);
+    if (!$form_state->isValueEmpty('variants')) {
+      foreach ($form_state->getValue('variants') as $variant_plugin_id => $data) {
+        if ($variant_plugin = $this->entity->getVariant($variant_plugin_id)) {
+          $variant_plugin->setWeight($data['weight']);
+          $variant_plugin->save();
         }
       }
     }
diff --git a/src/Form/PageVariantAddForm.php b/src/Form/PageVariantAddForm.php
new file mode 100644
index 0000000..3806b97
--- /dev/null
+++ b/src/Form/PageVariantAddForm.php
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\page_manager\Form\PageVariantAddForm.
+ */
+
+namespace Drupal\page_manager\Form;
+
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\page_manager\PageVariantInterface;
+
+/**
+ * Class PageVariantForm.
+ *
+ * @package Drupal\page_manager\Form
+ */
+class PageVariantAddForm extends PageVariantFormBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function submitText() {
+    return $this->t('Add variant');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function save(array $form, FormStateInterface $form_state) {
+    parent::save($form, $form_state);
+    $form_state->setRedirectUrl($this->getEntity()->urlInfo('edit-form'));
+  }
+
+}
diff --git a/src/Form/PageVariantConfigureForm.php b/src/Form/PageVariantConfigureForm.php
new file mode 100644
index 0000000..0304fa9
--- /dev/null
+++ b/src/Form/PageVariantConfigureForm.php
@@ -0,0 +1,67 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\page_manager\Form\PageVariantConfigureForm.
+ */
+
+namespace Drupal\page_manager\Form;
+
+use Drupal\Core\Form\FormBase;
+use Drupal\Core\Form\FormStateInterface;
+
+class PageVariantConfigureForm extends FormBase {
+
+  /**
+   * Returns a unique string identifying the form.
+   *
+   * @return string
+   *   The unique string identifying the form.
+   */
+  public function getFormId() {
+    // @todo this should vary by step/variant plugin id.
+    return 'page_manage_variant_configure_form';
+  }
+
+  /**
+   * Form constructor.
+   *
+   * @param array $form
+   *   An associative array containing the structure of the form.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   *
+   * @return array
+   *   The form structure.
+   */
+  public function buildForm(array $form, FormStateInterface $form_state) {
+    $cached_values = $form_state->getTemporaryValue('wizard');
+    /** @var \Drupal\page_manager\PageVariantInterface $page_variant */
+    $page_variant = $cached_values['variant'];
+    $variant = $page_variant->getVariantPlugin();
+    return $variant->buildConfigurationForm($form, $form_state);
+  }
+
+  /**
+   * Form submission handler.
+   *
+   * @param array $form
+   *   An associative array containing the structure of the form.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   */
+  public function submitForm(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['variant'];
+    $variant = $page_variant->getVariantPlugin();
+    $variant->submitConfigurationForm($form, $form_state);
+    $configuration = $variant->getConfiguration();
+    $page_variant->set('variant_settings', $configuration);
+    $page_variant->set('label', $configuration['label']);
+    $page->addVariantEntity($page_variant);
+  }
+
+}
diff --git a/src/Form/PageVariantDeleteForm.php b/src/Form/PageVariantDeleteForm.php
new file mode 100644
index 0000000..0aa7185
--- /dev/null
+++ b/src/Form/PageVariantDeleteForm.php
@@ -0,0 +1,62 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\page_manager\Form\PageVariantDeleteForm.
+ */
+
+namespace Drupal\page_manager\Form;
+
+use Drupal\Core\Entity\EntityConfirmFormBase;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Url;
+
+/**
+ * Builds the form to delete a PageVariant.
+ */
+class PageVariantDeleteForm extends EntityConfirmFormBase {
+
+  /**
+   * The entity being used by this form.
+   *
+   * @var \Drupal\page_manager\PageVariantInterface
+   */
+  protected $entity;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getQuestion() {
+    return $this->t('Are you sure you want to delete %name?', array('%name' => $this->entity->label()));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCancelUrl() {
+    return new Url('entity.page.edit_form', [
+      'page' => $this->entity->get('page'),
+    ]);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getConfirmText() {
+    return $this->t('Delete');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    $this->entity->delete();
+
+    drupal_set_message($this->t('The variant %label has been removed.', [
+      '%label' => $this->entity->label(),
+    ]));
+
+    $form_state->setRedirectUrl($this->getCancelUrl());
+  }
+
+}
diff --git a/src/Form/PageVariantEditForm.php b/src/Form/PageVariantEditForm.php
new file mode 100644
index 0000000..54faf95
--- /dev/null
+++ b/src/Form/PageVariantEditForm.php
@@ -0,0 +1,304 @@
+<?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_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_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_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_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_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_variant' => $page_variant->id(),
+          'condition_id' => $selection_id,
+        ]),
+        'attributes' => $attributes,
+      ];
+      $row['operations'] = [
+        '#type' => 'operations',
+        '#links' => $operations,
+      ];
+      $form['table'][$selection_id] = $row;
+    }
+
+    return $form;
+  }
+
+  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
new file mode 100644
index 0000000..ee1547f
--- /dev/null
+++ b/src/Form/PageVariantFormBase.php
@@ -0,0 +1,187 @@
+<?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 Drupal\page_manager\PageVariantInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Class PageVariantForm.
+ *
+ * @package Drupal\page_manager\Form
+ */
+abstract class PageVariantFormBase extends EntityForm {
+
+  /**
+   * 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;
+
+  /**
+   * Get the variant plugin for this page variant entity.
+   *
+   * @return \Drupal\Core\Display\VariantInterface
+   */
+  protected function getVariantPlugin() {
+    if (!$this->variantPlugin) {
+      /** @var PageVariantInterface $page_variant */
+      $page_variant = $this->getEntity();
+      $this->variantPlugin = $page_variant->getVariantPlugin();
+    }
+
+    return $this->variantPlugin;
+  }
+
+  /**
+   * 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);
+
+    /** @var PageVariantInterface $page_variant */
+    $page_variant = $this->getEntity();
+
+    $form['label'] = [
+      '#type' => 'textfield',
+      '#title' => $this->t('Label'),
+      '#description' => $this->t('The label for this variant.'),
+      '#default_value' => $page_variant->label() ?: (string) $this->getVariantPlugin()
+        ->adminLabel(),
+      '#maxlength' => '255',
+    ];
+
+    $form['id'] = [
+      '#type' => 'machine_name',
+      '#disabled' => !$page_variant->isNew(),
+      '#default_value' => !$page_variant->isNew() ? explode('.', $page_variant->id())[1] : '',
+      '#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) {
+    $this->prepareEntity();
+
+    /** @var PageVariantInterface $page_variant */
+    $page_variant = $this->getEntity();
+    return (bool) $this->entityQuery->get('page_variant')
+      ->condition('id', $page_variant->get('page') . '.' . $id)
+      ->execute();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function save(array $form, FormStateInterface $form_state) {
+    /** @var PageVariantInterface $page_variant */
+    $page_variant = $this->getEntity();
+    $page_variant->set('id', $page_variant->get('page') . '.' . $page_variant->id());
+    $status = $page_variant->save();
+
+    if ($status) {
+      drupal_set_message($this->t('Saved the %label variant.', [
+        '%label' => $page_variant->label(),
+      ]));
+    }
+    else {
+      drupal_set_message($this->t('The %label variant was not saved.', [
+        '%label' => $page_variant->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);
+  }
+
+}
diff --git a/src/Form/SelectionConditionDeleteForm.php b/src/Form/SelectionConditionDeleteForm.php
index f425f87..dbfdb2e 100644
--- a/src/Form/SelectionConditionDeleteForm.php
+++ b/src/Form/SelectionConditionDeleteForm.php
@@ -10,7 +10,7 @@
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Form\ConfirmFormBase;
 use Drupal\Core\Url;
-use Drupal\page_manager\PageInterface;
+use Drupal\page_manager\PageVariantInterface;
 
 /**
  * @todo.
@@ -20,16 +20,9 @@ class SelectionConditionDeleteForm extends ConfirmFormBase {
   /**
    * The page entity this selection condition belongs to.
    *
-   * @var \Drupal\page_manager\PageInterface
+   * @var \Drupal\page_manager\PageVariantInterface
    */
-  protected $page;
-
-  /**
-   * The display variant.
-   *
-   * @var \Drupal\ctools\Plugin\ConditionVariantInterface
-   */
-  protected $displayVariant;
+  protected $pageVariant;
 
   /**
    * The selection condition used by this form.
@@ -56,9 +49,8 @@ public function getQuestion() {
    * {@inheritdoc}
    */
   public function getCancelUrl() {
-    return new Url('page_manager.display_variant_edit', [
-      'page' => $this->page->id(),
-      'display_variant_id' => $this->displayVariant->id(),
+    return new Url('entity.page_variant.edit_form', [
+      'page_variant' => $this->pageVariant->id(),
     ]);
   }
 
@@ -72,10 +64,9 @@ public function getConfirmText() {
   /**
    * {@inheritdoc}
    */
-  public function buildForm(array $form, FormStateInterface $form_state, PageInterface $page = NULL, $display_variant_id = NULL, $condition_id = NULL) {
-    $this->page = $page;
-    $this->displayVariant = $this->page->getVariant($display_variant_id);
-    $this->selectionCondition = $this->displayVariant->getSelectionCondition($condition_id);
+  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);
   }
 
@@ -83,8 +74,8 @@ public function buildForm(array $form, FormStateInterface $form_state, PageInter
    * {@inheritdoc}
    */
   public function submitForm(array &$form, FormStateInterface $form_state) {
-    $this->displayVariant->removeSelectionCondition($this->selectionCondition->getConfiguration()['uuid']);
-    $this->page->save();
+    $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
index 59b6011..91c0ee2 100644
--- a/src/Form/SelectionConditionEditForm.php
+++ b/src/Form/SelectionConditionEditForm.php
@@ -23,8 +23,8 @@ public function getFormId() {
    * {@inheritdoc}
    */
   protected function prepareCondition($condition_id) {
-    // Load the selection condition directly from the display variant.
-    return $this->displayVariant->getSelectionCondition($condition_id);
+    // Load the selection condition directly from the variant.
+    return $this->pageVariant->getSelectionCondition($condition_id);
   }
 
   /**
diff --git a/src/Form/SelectionConditionFormBase.php b/src/Form/SelectionConditionFormBase.php
index 7a9017c..e809452 100644
--- a/src/Form/SelectionConditionFormBase.php
+++ b/src/Form/SelectionConditionFormBase.php
@@ -8,7 +8,7 @@
 namespace Drupal\page_manager\Form;
 
 use Drupal\Core\Form\FormStateInterface;
-use Drupal\page_manager\PageInterface;
+use Drupal\page_manager\PageVariantInterface;
 
 /**
  * Provides a base form for editing and adding a selection condition.
@@ -16,18 +16,18 @@
 abstract class SelectionConditionFormBase extends ConditionFormBase {
 
   /**
-   * The display variant.
+   * The page variant entity.
    *
-   * @var \Drupal\ctools\Plugin\ConditionVariantInterface
+   * @var \Drupal\page_manager\PageVariantInterface
    */
-  protected $displayVariant;
+  protected $pageVariant;
 
   /**
    * {@inheritdoc}
    */
-  public function buildForm(array $form, FormStateInterface $form_state, PageInterface $page = NULL, $display_variant_id = NULL, $condition_id = NULL) {
-    $this->displayVariant = $page->getVariant($display_variant_id);
-    return parent::buildForm($form, $form_state, $page, $condition_id);
+  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());
   }
 
   /**
@@ -39,15 +39,14 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
     $configuration = $this->condition->getConfiguration();
     // If this selection condition is new, add it to the page.
     if (!isset($configuration['uuid'])) {
-      $this->displayVariant->addSelectionCondition($configuration);
+      $this->pageVariant->addSelectionCondition($configuration);
     }
 
     // Save the page entity.
-    $this->page->save();
+    $this->pageVariant->save();
 
-    $form_state->setRedirect('page_manager.display_variant_edit', [
-      'page' => $this->page->id(),
-      'display_variant_id' => $this->displayVariant->id(),
+    $form_state->setRedirect('entity.page_variant.edit_form', [
+      'page_variant' => $this->pageVariant->id(),
     ]);
   }
 
diff --git a/src/Form/DisplayVariantAddBlockForm.php b/src/Form/VariantPluginAddBlockForm.php
similarity index 65%
rename from src/Form/DisplayVariantAddBlockForm.php
rename to src/Form/VariantPluginAddBlockForm.php
index 456f3c7..8b78cd8 100644
--- a/src/Form/DisplayVariantAddBlockForm.php
+++ b/src/Form/VariantPluginAddBlockForm.php
@@ -2,21 +2,21 @@
 
 /**
  * @file
- * Contains \Drupal\page_manager\Form\DisplayVariantAddBlockForm.
+ * Contains \Drupal\page_manager\Form\VariantPluginAddBlockForm.
  */
 
 namespace Drupal\page_manager\Form;
 
 use Drupal\Component\Plugin\PluginManagerInterface;
 use Drupal\Core\Form\FormStateInterface;
-use Drupal\page_manager\PageInterface;
+use Drupal\page_manager\PageVariantInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\HttpFoundation\Request;
 
 /**
- * Provides a form for adding a block plugin to a display variant.
+ * Provides a form for adding a block plugin to a variant.
  */
-class DisplayVariantAddBlockForm extends DisplayVariantConfigureBlockFormBase {
+class VariantPluginAddBlockForm extends VariantPluginConfigureBlockFormBase {
 
   /**
    * The block manager.
@@ -26,7 +26,7 @@ class DisplayVariantAddBlockForm extends DisplayVariantConfigureBlockFormBase {
   protected $blockManager;
 
   /**
-   * Constructs a new DisplayVariantFormBase.
+   * Constructs a new VariantPluginFormBase.
    *
    * @param \Drupal\Component\Plugin\PluginManagerInterface $block_manager
    *   The block manager.
@@ -48,7 +48,7 @@ public static function create(ContainerInterface $container) {
    * {@inheritdoc}
    */
   public function getFormId() {
-    return 'page_manager_display_variant_add_block_form';
+    return 'page_manager_variant_add_block_form';
   }
 
   /**
@@ -56,15 +56,15 @@ public function getFormId() {
    */
   protected function prepareBlock($plugin_id) {
     $block = $this->blockManager->createInstance($plugin_id);
-    $block_id = $this->displayVariant->addBlock($block->getConfiguration());
-    return $this->displayVariant->getBlock($block_id);
+    $block_id = $this->getVariantPlugin()->addBlock($block->getConfiguration());
+    return $this->getVariantPlugin()->getBlock($block_id);
   }
 
   /**
    * {@inheritdoc}
    */
-  public function buildForm(array $form, FormStateInterface $form_state, Request $request = NULL, PageInterface $page = NULL, $display_variant_id = NULL, $block_id = NULL) {
-    $form = parent::buildForm($form, $form_state, $page, $display_variant_id, $block_id);
+  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);
     $form['region']['#default_value'] = $request->query->get('region');
     return $form;
   }
diff --git a/src/Form/DisplayVariantConfigureBlockFormBase.php b/src/Form/VariantPluginConfigureBlockFormBase.php
similarity index 64%
rename from src/Form/DisplayVariantConfigureBlockFormBase.php
rename to src/Form/VariantPluginConfigureBlockFormBase.php
index 44ac43f..0aaef07 100644
--- a/src/Form/DisplayVariantConfigureBlockFormBase.php
+++ b/src/Form/VariantPluginConfigureBlockFormBase.php
@@ -2,7 +2,7 @@
 
 /**
  * @file
- * Contains \Drupal\page_manager\Form\DisplayVariantConfigureBlockFormBase.
+ * Contains \Drupal\page_manager\Form\VariantPluginConfigureBlockFormBase.
  */
 
 namespace Drupal\page_manager\Form;
@@ -12,28 +12,21 @@
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Plugin\ContextAwarePluginAssignmentTrait;
 use Drupal\Core\Plugin\ContextAwarePluginInterface;
-use Drupal\page_manager\PageInterface;
+use Drupal\page_manager\PageVariantInterface;
 
 /**
- * Provides a base form for configuring a block as part of a display variant.
+ * Provides a base form for configuring a block as part of a variant.
  */
-abstract class DisplayVariantConfigureBlockFormBase extends FormBase {
+abstract class VariantPluginConfigureBlockFormBase extends FormBase {
 
   use ContextAwarePluginAssignmentTrait;
 
   /**
    * The page entity.
    *
-   * @var \Drupal\page_manager\PageInterface
+   * @var \Drupal\page_manager\PageVariantInterface
    */
-  protected $page;
-
-  /**
-   * The display variant.
-   *
-   * @var \Drupal\ctools\Plugin\BlockVariantInterface
-   */
-  protected $displayVariant;
+  protected $pageVariant;
 
   /**
    * The plugin being configured.
@@ -64,11 +57,10 @@
   /**
    * {@inheritdoc}
    */
-  public function buildForm(array $form, FormStateInterface $form_state, PageInterface $page = NULL, $display_variant_id = NULL, $block_id = NULL) {
-    $this->page = $page;
-    $this->displayVariant = $page->getVariant($display_variant_id);
+  public function buildForm(array $form, FormStateInterface $form_state, PageVariantInterface $page_variant = NULL, $block_id = NULL) {
+    $this->pageVariant = $page_variant;
     $this->block = $this->prepareBlock($block_id);
-    $form_state->set('display_variant_id', $display_variant_id);
+    $form_state->set('page_variant_id', $page_variant->id());
     $form_state->set('block_id', $this->block->getConfiguration()['uuid']);
 
     $form['#tree'] = TRUE;
@@ -80,13 +72,13 @@ public function buildForm(array $form, FormStateInterface $form_state, PageInter
     $form['region'] = [
       '#title' => $this->t('Region'),
       '#type' => 'select',
-      '#options' => $this->displayVariant->getRegionNames(),
-      '#default_value' => $this->displayVariant->getRegionAssignment($this->block->getConfiguration()['uuid']),
+      '#options' => $this->getVariantPlugin()->getRegionNames(),
+      '#default_value' => $this->getVariantPlugin()->getRegionAssignment($this->block->getConfiguration()['uuid']),
       '#required' => TRUE,
     ];
 
     if ($this->block instanceof ContextAwarePluginInterface) {
-      $form['context_mapping'] = $this->addContextAssignmentElement($this->block, $this->page->getContexts());
+      $form['context_mapping'] = $this->addContextAssignmentElement($this->block, $this->pageVariant->getContexts());
     }
 
     $form['actions']['submit'] = [
@@ -101,10 +93,9 @@ public function buildForm(array $form, FormStateInterface $form_state, PageInter
    * {@inheritdoc}
    */
   public function validateForm(array &$form, FormStateInterface $form_state) {
-    // The page might have been serialized, resulting in a new display variant
-    // collection. Refresh the display variant and block objects.
-    $this->displayVariant = $this->page->getVariant($form_state->get('display_variant_id'));
-    $this->block = $this->displayVariant->getBlock($form_state->get('block_id'));
+    // The page might have been serialized, resulting in a new variant
+    // collection. Refresh the block object.
+    $this->block = $this->getVariantPlugin()->getBlock($form_state->get('block_id'));
 
     $settings = (new FormState())->setValues($form_state->getValue('settings'));
     // Call the plugin validate handler.
@@ -128,13 +119,21 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
       $this->block->setContextMapping($form_state->getValue('context_mapping', []));
     }
 
-    $this->displayVariant->updateBlock($this->block->getConfiguration()['uuid'], ['region' => $form_state->getValue('region')]);
-    $this->page->save();
+    $this->getVariantPlugin()->updateBlock($this->block->getConfiguration()['uuid'], ['region' => $form_state->getValue('region')]);
+    $this->pageVariant->save();
 
-    $form_state->setRedirect('page_manager.display_variant_edit', [
-      'page' => $this->page->id(),
-      'display_variant_id' => $this->displayVariant->id(),
+    $form_state->setRedirect('entity.page_variant.edit_form', [
+      'page_variant' => $this->pageVariant->id(),
     ]);
   }
 
+  /**
+   * @todo.
+   *
+   * @return \Drupal\ctools\Plugin\BlockVariantInterface
+   */
+  protected function getVariantPlugin() {
+    return $this->pageVariant->getVariantPlugin();
+  }
+
 }
diff --git a/src/Form/DisplayVariantDeleteBlockForm.php b/src/Form/VariantPluginDeleteBlockForm.php
similarity index 57%
rename from src/Form/DisplayVariantDeleteBlockForm.php
rename to src/Form/VariantPluginDeleteBlockForm.php
index d4fba53..693f8ba 100644
--- a/src/Form/DisplayVariantDeleteBlockForm.php
+++ b/src/Form/VariantPluginDeleteBlockForm.php
@@ -2,7 +2,7 @@
 
 /**
  * @file
- * Contains \Drupal\page_manager\Form\DisplayVariantDeleteBlockForm.
+ * Contains \Drupal\page_manager\Form\VariantPluginDeleteBlockForm.
  */
 
 namespace Drupal\page_manager\Form;
@@ -10,26 +10,19 @@
 use Drupal\Core\Form\ConfirmFormBase;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Url;
-use Drupal\page_manager\PageInterface;
+use Drupal\page_manager\PageVariantInterface;
 
 /**
  * Provides a form for deleting an access condition.
  */
-class DisplayVariantDeleteBlockForm extends ConfirmFormBase {
+class VariantPluginDeleteBlockForm extends ConfirmFormBase {
 
   /**
-   * The page entity.
+   * The page variant.
    *
-   * @var \Drupal\page_manager\PageInterface
+   * @var \Drupal\page_manager\PageVariantInterface
    */
-  protected $page;
-
-  /**
-   * The display variant.
-   *
-   * @var \Drupal\ctools\Plugin\BlockVariantInterface
-   */
-  protected $displayVariant;
+  protected $pageVariant;
 
   /**
    * The plugin being configured.
@@ -42,7 +35,7 @@ class DisplayVariantDeleteBlockForm extends ConfirmFormBase {
    * {@inheritdoc}
    */
   public function getFormId() {
-    return 'page_manager_display_variant_delete_block_form';
+    return 'page_manager_variant_delete_block_form';
   }
 
   /**
@@ -56,9 +49,8 @@ public function getQuestion() {
    * {@inheritdoc}
    */
   public function getCancelUrl() {
-    return new Url('page_manager.display_variant_edit', [
-      'page' => $this->page->id(),
-      'display_variant_id' => $this->displayVariant->id()
+    return new Url('entity.page_variant.edit_form', [
+      'page_variant' => $this->pageVariant->id(),
     ]);
   }
 
@@ -72,10 +64,9 @@ public function getConfirmText() {
   /**
    * {@inheritdoc}
    */
-  public function buildForm(array $form, FormStateInterface $form_state, PageInterface $page = NULL, $display_variant_id = NULL, $block_id = NULL) {
-    $this->page = $page;
-    $this->displayVariant = $this->page->getVariant($display_variant_id);
-    $this->block = $this->displayVariant->getBlock($block_id);
+  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);
     return parent::buildForm($form, $form_state);
   }
 
@@ -83,11 +74,20 @@ public function buildForm(array $form, FormStateInterface $form_state, PageInter
    * {@inheritdoc}
    */
   public function submitForm(array &$form, FormStateInterface $form_state) {
-    $this->displayVariant->removeBlock($this->block->getConfiguration()['uuid']);
-    $this->page->save();
+    $this->getVariantPlugin()->removeBlock($this->block->getConfiguration()['uuid']);
+    $this->pageVariant->save();
     drupal_set_message($this->t('The block %label has been removed.', ['%label' => $this->block->label()]));
 
     $form_state->setRedirectUrl($this->getCancelUrl());
   }
 
+  /**
+   * @todo.
+   *
+   * @return \Drupal\ctools\Plugin\BlockVariantInterface
+   */
+  protected function getVariantPlugin() {
+    return $this->pageVariant->getVariantPlugin();
+  }
+
 }
diff --git a/src/Form/DisplayVariantEditBlockForm.php b/src/Form/VariantPluginEditBlockForm.php
similarity index 50%
rename from src/Form/DisplayVariantEditBlockForm.php
rename to src/Form/VariantPluginEditBlockForm.php
index 1bb4532..18bc7d6 100644
--- a/src/Form/DisplayVariantEditBlockForm.php
+++ b/src/Form/VariantPluginEditBlockForm.php
@@ -2,28 +2,28 @@
 
 /**
  * @file
- * Contains \Drupal\page_manager\Form\DisplayVariantEditBlockForm.
+ * Contains \Drupal\page_manager\Form\VariantPluginEditBlockForm.
  */
 
 namespace Drupal\page_manager\Form;
 
 /**
- * Provides a form for editing a block plugin of a display variant.
+ * Provides a form for editing a block plugin of a variant.
  */
-class DisplayVariantEditBlockForm extends DisplayVariantConfigureBlockFormBase {
+class VariantPluginEditBlockForm extends VariantPluginConfigureBlockFormBase {
 
   /**
    * {@inheritdoc}
    */
   public function getFormId() {
-    return 'page_manager_display_variant_edit_block_form';
+    return 'page_manager_variant_edit_block_form';
   }
 
   /**
    * {@inheritdoc}
    */
   protected function prepareBlock($block_id) {
-    return $this->displayVariant->getBlock($block_id);
+    return $this->getVariantPlugin()->getBlock($block_id);
   }
 
   /**
diff --git a/src/PageExecutable.php b/src/PageExecutable.php
index 63ec5b8..22a21d2 100644
--- a/src/PageExecutable.php
+++ b/src/PageExecutable.php
@@ -69,11 +69,12 @@ public function addContext($name, ContextInterface $value) {
    * {@inheritdoc}
    */
   public function getRuntimeVariant($variant_id) {
-    $variant = $this->getPage()->getVariant($variant_id);
-    if ($variant instanceof ContextAwareVariantInterface) {
-      $variant->setContexts($this->getContexts());
+    $page_variant = $this->getPage()->getVariant($variant_id);
+    $variant_plugin = $page_variant->getVariantPlugin();
+    if ($variant_plugin instanceof ContextAwareVariantInterface) {
+      $variant_plugin->setContexts($this->getContexts());
     }
-    return $variant;
+    return $page_variant;
   }
 
   /**
diff --git a/src/PageExecutableInterface.php b/src/PageExecutableInterface.php
index 4d64f17..1174a5c 100644
--- a/src/PageExecutableInterface.php
+++ b/src/PageExecutableInterface.php
@@ -52,8 +52,8 @@ public function addContext($name, ContextInterface $value);
    * @param string $variant_id
    *   The variant ID.
    *
-   * @return \Drupal\Core\Display\VariantInterface
-   *   The variant object.
+   * @return \Drupal\page_manager\PageVariantInterface
+   *   The page variant entity.
    */
   public function getRuntimeVariant($variant_id);
 
diff --git a/src/PageInterface.php b/src/PageInterface.php
index 59bc383..5a16b5e 100644
--- a/src/PageInterface.php
+++ b/src/PageInterface.php
@@ -9,12 +9,11 @@
 
 use Drupal\Core\Config\Entity\ConfigEntityInterface;
 use Drupal\Core\Entity\EntityWithPluginCollectionInterface;
-use Drupal\page_manager\Plugin\VariantAwareInterface;
 
 /**
  * Provides an interface for page entities.
  */
-interface PageInterface extends ConfigEntityInterface, EntityWithPluginCollectionInterface, VariantAwareInterface {
+interface PageInterface extends ConfigEntityInterface, EntityWithPluginCollectionInterface {
 
   /**
    * Returns whether the page entity is enabled.
@@ -48,6 +47,55 @@ public function getPath();
   public function usesAdminTheme();
 
   /**
+   * Adds a new variant to the entity.
+   *
+   * @param array $configuration
+   *   An array of configuration for the new variant.
+   *
+   * @return string
+   *   The variant ID.
+   */
+  public function addVariant(array $configuration);
+
+  /**
+   * @todo.
+   *
+   * @param \Drupal\page_manager\PageVariantInterface $variant
+   *
+   * @return string
+   */
+  public function addVariantEntity(PageVariantInterface $variant);
+
+  /**
+   * Retrieves a specific variant.
+   *
+   * @param string $variant_id
+   *   The variant ID.
+   *
+   * @return \Drupal\page_manager\PageVariantInterface
+   *   The variant object.
+   */
+  public function getVariant($variant_id);
+
+  /**
+   * Removes a specific variant.
+   *
+   * @param string $variant_id
+   *   The variant ID.
+   *
+   * @return $this
+   */
+  public function removeVariant($variant_id);
+
+  /**
+   * Returns the variants available for the entity.
+   *
+   * @return \Drupal\page_manager\PageVariantInterface[]
+   *   An array of the variants.
+   */
+  public function getVariants();
+
+  /**
    * Returns the conditions used for determining access for this page entity.
    *
    * @return \Drupal\Core\Condition\ConditionInterface[]|\Drupal\Core\Condition\ConditionPluginCollection
diff --git a/src/PageVariantInterface.php b/src/PageVariantInterface.php
new file mode 100644
index 0000000..6dfd2bc
--- /dev/null
+++ b/src/PageVariantInterface.php
@@ -0,0 +1,112 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\page_manager\PageVariantInterface.
+ */
+
+namespace Drupal\page_manager;
+
+use Drupal\Core\Config\Entity\ConfigEntityInterface;
+use Drupal\Core\Entity\EntityWithPluginCollectionInterface;
+use Drupal\Core\Session\AccountInterface;
+
+/**
+ * Provides an interface defining a PageVariant entity.
+ */
+interface PageVariantInterface extends ConfigEntityInterface, EntityWithPluginCollectionInterface {
+
+  /**
+   * Gets the variant plugin.
+   *
+   * @return \Drupal\Core\Display\VariantInterface
+   */
+  public function getVariantPlugin();
+
+  /**
+   * Gets the plugin id of the variant plugin.
+   *
+   * @return string
+   */
+  public function getVariantPluginId();
+
+  /**
+   * Gets the values for all defined contexts.
+   *
+   * @return \Drupal\Core\Plugin\Context\ContextInterface[]
+   *   An array of set context values, keyed by context name.
+   */
+  public function getContexts();
+
+  /**
+   * Gets the weight of this variant (compared to other variants on the page).
+   *
+   * @return int
+   */
+  public function getWeight();
+
+  /**
+   * Sets the weight of this variant (compared to other variants on the page).
+   *
+   * @param int $weight
+   *   The weight of the variant.
+   *
+   * @return $this
+   */
+  public function setWeight($weight);
+
+   /**
+   * Gets the selection condition collection.
+   *
+   * @return \Drupal\Core\Condition\ConditionInterface[]|\Drupal\Core\Condition\ConditionPluginCollection
+   */
+  public function getSelectionConditions();
+
+  /**
+   * Adds selection criteria.
+   *
+   * @param array $configuration
+   *   Configuration of the selection criteria.
+   *
+   * @return string
+   *   The condition id of the new criteria.
+   */
+  public function addSelectionCondition(array $configuration);
+
+  /**
+   * Gets selection criteria by condition id.
+   *
+   * @param string $condition_id
+   *   The id of the condition.
+   *
+   * @return \Drupal\Core\Condition\ConditionInterface
+   */
+  public function getSelectionCondition($condition_id);
+
+  /**
+   * Removes selection criteria by condition id.
+   *
+   * @param string $condition_id
+   *   The id of the condition.
+   *
+   * @return $this
+   */
+  public function removeSelectionCondition($condition_id);
+
+  /**
+   * Gets the selection logic used by the criteria (ie. "and" or "or").
+   *
+   * @return string
+   *   Either "and" or "or"; represents how the selection criteria are combined.
+   */
+  public function getSelectionLogic();
+
+  /**
+   * Checks if this page variant entity is selectable.
+   *
+   * @return bool
+   *   TRUE if the variant is selectable; otherwise FALSE.
+   */
+  public function canSelect(AccountInterface $account = NULL);
+
+}
diff --git a/src/Plugin/DisplayVariant/HttpStatusCodeDisplayVariant.php b/src/Plugin/DisplayVariant/HttpStatusCodeDisplayVariant.php
index cd9246c..debb511 100644
--- a/src/Plugin/DisplayVariant/HttpStatusCodeDisplayVariant.php
+++ b/src/Plugin/DisplayVariant/HttpStatusCodeDisplayVariant.php
@@ -13,7 +13,7 @@
 use Symfony\Component\HttpKernel\Exception\HttpException;
 
 /**
- * Provides a display variant that returns a response with an HTTP status code.
+ * Provides a variant that returns a response with an HTTP status code.
  *
  * @DisplayVariant(
  *   id = "http_status_code",
@@ -26,7 +26,10 @@ class HttpStatusCodeDisplayVariant extends VariantBase {
    * {@inheritdoc}
    */
   public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
-    $form = parent::buildConfigurationForm($form, $form_state);
+    // Don't call VariantBase::buildConfigurationForm() on purpose, because it
+    // adds a 'Label' field that we don't actually want to use - we store the
+    // label on the page variant entity.
+    //$form = parent::buildConfigurationForm($form, $form_state);
 
     // Get all possible status codes defined by Symfony.
     $options = Response::$statusTexts;
diff --git a/src/Plugin/DisplayVariant/PageBlockDisplayVariant.php b/src/Plugin/DisplayVariant/PageBlockDisplayVariant.php
index 1da150c..6ef363d 100644
--- a/src/Plugin/DisplayVariant/PageBlockDisplayVariant.php
+++ b/src/Plugin/DisplayVariant/PageBlockDisplayVariant.php
@@ -16,7 +16,7 @@
 use Drupal\ctools\Plugin\DisplayVariant\BlockDisplayVariant;
 
 /**
- * Provides a display variant that simply contains blocks.
+ * Provides a variant plugin that simply contains blocks.
  *
  * @DisplayVariant(
  *   id = "block_display",
@@ -145,7 +145,10 @@ public function buildBlock($build) {
    * {@inheritdoc}
    */
   public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
-    $form = parent::buildConfigurationForm($form, $form_state);
+    // Don't call VariantBase::buildConfigurationForm() on purpose, because it
+    // adds a 'Label' field that we don't actually want to use - we store the
+    // label on the page variant entity.
+    //$form = parent::buildConfigurationForm($form, $form_state);
 
     // Allow to configure the page title, even when adding a new display.
     // Default to the page label in that case.
diff --git a/src/Plugin/VariantAwareInterface.php b/src/Plugin/VariantAwareInterface.php
deleted file mode 100644
index 0226fb7..0000000
--- a/src/Plugin/VariantAwareInterface.php
+++ /dev/null
@@ -1,54 +0,0 @@
-<?php
-/**
- * @file
- * Contains \Drupal\page_manager\Plugin\VariantAwareInterface.
- */
-
-namespace Drupal\page_manager\Plugin;
-
-/**
- * Provides an interface for objects that have variants e.g. Pages.
- */
-interface VariantAwareInterface {
-
-  /**
-   * Adds a new variant to the entity.
-   *
-   * @param array $configuration
-   *   An array of configuration for the new variant.
-   *
-   * @return string
-   *   The variant ID.
-   */
-  public function addVariant(array $configuration);
-
-  /**
-   * Retrieves a specific variant.
-   *
-   * @param string $variant_id
-   *   The variant ID.
-   *
-   * @return \Drupal\Core\Display\VariantInterface
-   *   The variant object.
-   */
-  public function getVariant($variant_id);
-
-  /**
-   * Removes a specific variant.
-   *
-   * @param string $variant_id
-   *   The variant ID.
-   *
-   * @return $this
-   */
-  public function removeVariant($variant_id);
-
-  /**
-   * Returns the variants available for the entity.
-   *
-   * @return \Drupal\Core\Display\VariantInterface[]
-   *   An array of the variants.
-   */
-  public function getVariants();
-
-}
diff --git a/src/Plugin/VariantAwareTrait.php b/src/Plugin/VariantAwareTrait.php
deleted file mode 100644
index 95677a9..0000000
--- a/src/Plugin/VariantAwareTrait.php
+++ /dev/null
@@ -1,71 +0,0 @@
-<?php
-/**
- * @file
- * Contains \Drupal\page_manager\Plugin\VariantAwareTrait.
- */
-
-namespace Drupal\page_manager\Plugin;
-
-/**
- * Provides methods for Page Manager entities that need to handle its variants.
- */
-trait VariantAwareTrait {
-
-  /**
-   * The plugin collection that holds the variants.
-   *
-   * @var \Drupal\page_manager\Plugin\VariantCollection
-   */
-  protected $variantCollection;
-
-  /**
-   * @see \Drupal\page_manager\Plugin\VariantAwareInterface::addVariant()
-   */
-  public function addVariant(array $configuration) {
-    $configuration['uuid'] = $this->uuidGenerator()->generate();
-    $this->getVariants()->addInstanceId($configuration['uuid'], $configuration);
-    return $configuration['uuid'];
-  }
-
-  /**
-   * @see \Drupal\page_manager\Plugin\VariantAwareInterface::getVariant()
-   */
-  public function getVariant($variant_id) {
-    return $this->getVariants()->get($variant_id);
-  }
-
-  /**
-   * @see \Drupal\page_manager\Plugin\VariantAwareInterface::removeVariant()
-   */
-  public function removeVariant($variant_id) {
-    $this->getVariants()->removeInstanceId($variant_id);
-    return $this;
-  }
-
-  /**
-   * @see \Drupal\page_manager\Plugin\VariantAwareInterface::getVariants()
-   */
-  public function getVariants() {
-    if (!$this->variantCollection) {
-      $this->variantCollection = new VariantCollection(\Drupal::service('plugin.manager.display_variant'), $this->getVariantConfig());
-      $this->variantCollection->sort();
-    }
-    return $this->variantCollection;
-  }
-
-  /**
-   * Returns the configuration for stored variants.
-   *
-   * @return array
-   *   An array of variant configuration, keyed by the unique variant ID.
-   */
-  abstract protected function getVariantConfig();
-
-  /**
-   * Returns the UUID generator.
-   *
-   * @return \Drupal\Component\Uuid\UuidInterface
-   */
-  abstract protected function uuidGenerator();
-
-}
diff --git a/src/Plugin/VariantCollection.php b/src/Plugin/VariantCollection.php
deleted file mode 100644
index 55819e0..0000000
--- a/src/Plugin/VariantCollection.php
+++ /dev/null
@@ -1,48 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\page_manager\Plugin\VariantCollection.
- */
-
-namespace Drupal\page_manager\Plugin;
-
-use Drupal\Core\Plugin\DefaultLazyPluginCollection;
-
-/**
- * Provides a collection of variants plugins.
- */
-class VariantCollection extends DefaultLazyPluginCollection {
-
-  /**
-   * {@inheritdoc}
-   *
-   * @return \Drupal\Core\Display\VariantInterface
-   */
-  public function &get($instance_id) {
-    return parent::get($instance_id);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function sort() {
-    // @todo Determine the reason this needs error suppression.
-    @uasort($this->instanceIDs, [$this, 'sortHelper']);
-    return $this;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function sortHelper($aID, $bID) {
-    $a_weight = $this->get($aID)->getWeight();
-    $b_weight = $this->get($bID)->getWeight();
-    if ($a_weight == $b_weight) {
-      return 0;
-    }
-
-    return ($a_weight < $b_weight) ? -1 : 1;
-  }
-
-}
diff --git a/src/Routing/VariantRouteFilter.php b/src/Routing/VariantRouteFilter.php
index 4eba226..120125c 100644
--- a/src/Routing/VariantRouteFilter.php
+++ b/src/Routing/VariantRouteFilter.php
@@ -124,10 +124,10 @@ protected function processRoute(Route $route, $name, RouteCollection $collection
 
     /** @var \Drupal\page_manager\PageInterface $page */
     $page = $this->pageStorage->load($defaults['page_manager_page']);
-    $variant = $page->getExecutable()->getRuntimeVariant($defaults['variant_id']);
+    $page_variant = $page->getExecutable()->getRuntimeVariant($defaults['variant_id']);
 
     try {
-      $access = $variant->access();
+      $access = $page_variant->canSelect();
     }
     // Since access checks can throw a context exception, consider that as
     // a disallowed variant.
diff --git a/src/Tests/PageConfigSchemaTest.php b/src/Tests/PageConfigSchemaTest.php
index 0fa845f..33c41cb 100644
--- a/src/Tests/PageConfigSchemaTest.php
+++ b/src/Tests/PageConfigSchemaTest.php
@@ -48,17 +48,20 @@ public function testValidPageConfigSchema() {
         'node' => 'node',
       ],
     ]);
+    $page->save();
 
-    // Add a block display variant.
-    $display_variant_id = $page->addVariant([
-      'id' => 'block_display',
+    // Add a block variant.
+    $variant_plugin_id = $page->addVariant([
+      'variant' => 'block_display',
+      'id' => 'block_page',
       'label' => 'Block page',
     ]);
-    /** @var \Drupal\page_manager\Plugin\DisplayVariant\PageBlockDisplayVariant $display_variant */
-    $display_variant = $page->getVariant($display_variant_id);
+    $page_variant = $page->getVariant($variant_plugin_id);
+    /** @var \Drupal\page_manager\Plugin\DisplayVariant\PageBlockDisplayVariant $variant_plugin */
+    $variant_plugin = $page_variant->getVariantPlugin();
 
     // Add a selection condition.
-    $display_variant->addSelectionCondition([
+    $page_variant->addSelectionCondition([
       'id' => 'node_type',
       'bundles' => [
         'page' => 'page',
@@ -69,18 +72,22 @@ public function testValidPageConfigSchema() {
     ]);
 
     // Add a block.
-    $display_variant->addBlock([
+    $variant_plugin->addBlock([
       'id' => 'entity_view:node',
       'label' => 'View the node',
       'provider' => 'page_manager',
       'label_display' => 'visible',
       'view_mode' => 'default',
     ]);
-    $page->save();
+    $page_variant->save();
 
-    $config = \Drupal::config("page_manager.page.$id");
-    $this->assertEqual($config->get('id'), $id);
-    $this->assertConfigSchema(\Drupal::service('config.typed'), $config->getName(), $config->get());
+    $page_config = \Drupal::config("page_manager.page.$id");
+    $this->assertEqual($page_config->get('id'), $id);
+    $variant_config = \Drupal::config("page_manager.page_variant.$variant_plugin_id");
+    $this->assertEqual($variant_config->get('id'), $variant_plugin_id);
+
+    $this->assertConfigSchema(\Drupal::service('config.typed'), $page_config->getName(), $page_config->get());
+    $this->assertConfigSchema(\Drupal::service('config.typed'), $variant_config->getName(), $variant_config->get());
   }
 
 }
diff --git a/src/Tests/PageManagerAdminTest.php b/src/Tests/PageManagerAdminTest.php
index d7ef570..37e5bdc 100644
--- a/src/Tests/PageManagerAdminTest.php
+++ b/src/Tests/PageManagerAdminTest.php
@@ -52,20 +52,19 @@ protected function setUp() {
   public function testAdmin() {
     $this->doTestAddPage();
     $this->doTestDisablePage();
-    $this->doTestAddDisplayVariant();
+    $this->doTestAddVariant();
     $this->doTestAddBlock();
     $this->doTestEditBlock();
-    $this->doTestEditDisplayVariant();
-    $this->doTestReorderDisplayVariants();
+    $this->doTestEditVariant();
+    $this->doTestReorderVariants();
     $this->doTestAddPageWithDuplicatePath();
     $this->doTestAdminPath();
-    $this->doTestRemoveDisplayVariant();
+    $this->doTestRemoveVariant();
     $this->doTestRemoveBlock();
     $this->doTestAddBlockWithAjax();
     $this->doTestEditBlock();
     $this->doTestExistingPathWithoutParameters();
     $this->doTestDeletePage();
-    $this->doTestExistingRoutes();
   }
 
   /**
@@ -94,18 +93,31 @@ protected function doTestAddPage() {
     $this->drupalGet('admin/foo');
     $this->assertResponse(404);
     $this->drupalGet('admin/structure/page_manager/manage/foo');
-    $this->clickLink('Edit');
-    $this->drupalPostForm(NULL, ['display_variant[status_code]' => 200], 'Update display variant');
+    $this->clickLink('Add new variant');
+    $this->clickLink('HTTP status code');
+    $edit = [
+      'id' => 'http_status_code',
+      'label' => 'Default',
+      'variant_settings[status_code]' => 200,
+    ];
+    $this->drupalPostForm(NULL, $edit, 'Save');
     $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, ['display_variant[status_code]' => 403], 'Update display variant');
+    $this->drupalPostForm(NULL, ['variant_settings[status_code]' => 403], 'Save');
 
-    // Assert that a display variant was added by default.
+    // Set the weight of the 'Default' variant to 10.
+    $default_variant = $this->findVariantByLabel('foo', 'Default');
+    $edit = [
+      'variants[' . $default_variant->uuid() . '][weight]' => 10,
+    ];
+    $this->drupalPostForm(NULL, $edit, 'Save');
+
+    // Assert that a variant was added by default.
     $this->drupalGet('admin/structure/page_manager/manage/foo');
-    $this->assertNoText('There are no display variants.');
+    $this->assertNoText('There are no variants.');
   }
 
   /**
@@ -129,20 +141,21 @@ protected function doTestDisablePage() {
   }
 
   /**
-   * Tests adding a display variant.
+   * Tests adding a variant.
    */
-  protected function doTestAddDisplayVariant() {
+  protected function doTestAddVariant() {
     $this->drupalGet('admin/structure/page_manager/manage/foo');
 
-    // Add a new display variant.
-    $this->clickLink('Add new display variant');
+    // Add a new variant.
+    $this->clickLink('Add new variant');
     $this->clickLink('Block page');
     $edit = [
-      'display_variant[label]' => 'First',
-      'display_variant[page_title]' => 'Example title',
+      'label' => 'First',
+      'id' => 'block_page',
+      'variant_settings[page_title]' => 'Example title',
     ];
-    $this->drupalPostForm(NULL, $edit, 'Add display variant');
-    $this->assertRaw(new FormattableMarkup('The %label display variant has been added.', ['%label' => 'First']));
+    $this->drupalPostForm(NULL, $edit, 'Save');
+    $this->assertRaw(new FormattableMarkup('Saved the %label variant.', ['%label' => 'First']));
 
     // Test that the variant is still used but empty.
     $this->drupalGet('admin/foo');
@@ -206,9 +219,9 @@ protected function doTestEditBlock() {
   }
 
   /**
-   * Tests editing a display variant.
+   * Tests editing a variant.
    */
-  protected function doTestEditDisplayVariant() {
+  protected function doTestEditVariant() {
     if (!$block = $this->findBlockByLabel('foo', 'First', 'Updated block label')) {
       $this->fail('Block not found');
       return;
@@ -217,27 +230,27 @@ protected function doTestEditDisplayVariant() {
     $block_config = $block->getConfiguration();
     $this->drupalGet('admin/structure/page_manager/manage/foo');
     $this->clickLink('Edit');
-    $this->assertTitle('Edit First display variant | Drupal');
+    $this->assertTitle('Edit First variant | Drupal');
 
-    $this->assertOptionSelected('edit-display-variant-blocks-' . $block_config['uuid'] . '-region', 'top');
-    $this->assertOptionSelected('edit-display-variant-blocks-' . $block_config['uuid'] . '-weight', 0);
+    $this->assertOptionSelected('edit-variant-plugin-blocks-' . $block_config['uuid'] . '-region', 'top');
+    $this->assertOptionSelected('edit-variant-plugin-blocks-' . $block_config['uuid'] . '-weight', 0);
 
-    $form_name = 'display_variant[blocks][' . $block_config['uuid'] . ']';
+    $form_name = 'variant_plugin[blocks][' . $block_config['uuid'] . ']';
     $edit = [
       $form_name . '[region]' => 'bottom',
       $form_name . '[weight]' => -10,
     ];
-    $this->drupalPostForm(NULL, $edit, 'Update display variant');
-    $this->assertRaw(new FormattableMarkup('The %label display variant has been updated.', ['%label' => 'First']));
+    $this->drupalPostForm(NULL, $edit, 'Save');
+    $this->assertRaw(new FormattableMarkup('Saved the %label variant.', ['%label' => 'First']));
     $this->clickLink('Edit');
-    $this->assertOptionSelected('edit-display-variant-blocks-' . $block_config['uuid'] . '-region', 'bottom');
-    $this->assertOptionSelected('edit-display-variant-blocks-' . $block_config['uuid'] . '-weight', -10);
+    $this->assertOptionSelected('edit-variant-plugin-blocks-' . $block_config['uuid'] . '-region', 'bottom');
+    $this->assertOptionSelected('edit-variant-plugin-blocks-' . $block_config['uuid'] . '-weight', -10);
   }
 
   /**
-   * Tests reordering display variants.
+   * Tests reordering variants.
    */
-  protected function doTestReorderDisplayVariants() {
+  protected function doTestReorderVariants() {
     $this->drupalGet('admin/foo');
     $this->assertResponse(200);
     $elements = $this->xpath('//div[@class="block-region-bottom"]/nav/ul[@class="menu"]/li/a');
@@ -248,9 +261,9 @@ protected function doTestReorderDisplayVariants() {
     }
     $this->assertEqual($expected, $links);
 
-    $display_variant = $this->findDisplayVariantByLabel('foo', 'Default');
+    $variant_plugin = $this->findVariantByLabel('foo', 'Default');
     $edit = [
-      'display_variants[' . $display_variant->id() . '][weight]' => -10,
+      'variants[' . $variant_plugin->uuid() . '][weight]' => -10,
     ];
     $this->drupalPostForm('admin/structure/page_manager/manage/foo', $edit, 'Save');
     $this->drupalGet('admin/foo');
@@ -293,14 +306,14 @@ protected function doTestAdminPath() {
   }
 
   /**
-   * Tests removing a display variant.
+   * Tests removing a variant.
    */
-  protected function doTestRemoveDisplayVariant() {
+  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 the display variant %label?', ['%label' => 'Default']));
+    $this->assertRaw(new FormattableMarkup('Are you sure you want to delete %label?', ['%label' => 'Default']));
     $this->drupalPostForm(NULL, [], 'Delete');
-    $this->assertRaw(new FormattableMarkup('The display variant %label has been removed.', ['%label' => 'Default']));
+    $this->assertRaw(new FormattableMarkup('The variant %label has been removed.', ['%label' => 'Default']));
   }
 
   /**
@@ -407,8 +420,7 @@ protected function doTestDeletePage() {
   /**
    * Tests that default arguments are not removed from existing routes.
    */
-  public function doTestExistingRoutes() {
-
+  public function testExistingRoutes() {
     // Test that the page without placeholder is accessible.
     $edit = [
       'label' => 'Placeholder test 2',
@@ -417,6 +429,18 @@ public function doTestExistingRoutes() {
     ];
     $this->drupalPostForm('admin/structure/page_manager/add', $edit, 'Save');
     $this->drupalGet('page-manager-test');
+    $this->assertResponse(200);
+
+    $this->drupalGet('admin/structure/page_manager/manage/placeholder2');
+    $this->clickLink('Add new variant');
+    $this->clickLink('HTTP status code');
+    $edit = [
+      'id' => 'http_status_code',
+      'label' => 'Default',
+      'variant_settings[status_code]' => 404,
+    ];
+    $this->drupalPostForm(NULL, $edit, 'Save');
+    $this->drupalGet('page-manager-test');
     $this->assertResponse(404);
 
     // Test that the page test is accessible.
@@ -442,18 +466,19 @@ protected function assertTheme($theme_name) {
    *
    * @param string $page_id
    *   The ID of the page entity.
-   * @param string $display_variant_label
-   *   The label of the display variant.
+   * @param string $variant_label
+   *   The label of the variant.
    * @param string $block_label
    *   The label of the block.
    *
    * @return \Drupal\Core\Block\BlockPluginInterface|null
    *   Either a block plugin, or NULL.
    */
-  protected function findBlockByLabel($page_id, $display_variant_label, $block_label) {
-    if ($display_variant = $this->findDisplayVariantByLabel($page_id, $display_variant_label)) {
-      /** @var \Drupal\ctools\Plugin\BlockVariantInterface $display_variant */
-      foreach ($display_variant->getRegionAssignments() as $blocks) {
+  protected function findBlockByLabel($page_id, $variant_label, $block_label) {
+    if ($page_variant = $this->findVariantByLabel($page_id, $variant_label)) {
+      /** @var \Drupal\ctools\Plugin\BlockVariantInterface $variant_plugin */
+      $variant_plugin = $page_variant->getVariantPlugin();
+      foreach ($variant_plugin->getRegionAssignments() as $blocks) {
         /** @var \Drupal\Core\Block\BlockPluginInterface[] $blocks */
         foreach ($blocks as $block) {
           if ($block->label() == $block_label) {
@@ -466,22 +491,22 @@ protected function findBlockByLabel($page_id, $display_variant_label, $block_lab
   }
 
   /**
-   * Finds a display variant based on its page and display variant label.
+   * Finds a variant based on its page and variant label.
    *
    * @param string $page_id
    *   The ID of the page entity.
-   * @param string $display_variant_label
-   *   The label of the display variant.
+   * @param string $variant_label
+   *   The label of the variant.
    *
-   * @return \Drupal\Core\Display\VariantInterface|null
-   *   Either a display variant, or NULL.
+   * @return \Drupal\page_manager\PageVariantInterface|NULL
+   *   Either a variant, or NULL.
    */
-  protected function findDisplayVariantByLabel($page_id, $display_variant_label) {
+  protected function findVariantByLabel($page_id, $variant_label) {
     if ($page = Page::load($page_id)) {
       /** @var \Drupal\page_manager\PageInterface $page */
-      foreach ($page->getVariants() as $display_variant) {
-        if ($display_variant->label() == $display_variant_label) {
-          return $display_variant;
+      foreach ($page->getVariants() as $page_variant) {
+        if ($page_variant->label() == $variant_label) {
+          return $page_variant;
         }
       }
     }
diff --git a/src/Tests/PageManagerTranslationIntegrationTest.php b/src/Tests/PageManagerTranslationIntegrationTest.php
index 14c9ad6..1c5b47c 100644
--- a/src/Tests/PageManagerTranslationIntegrationTest.php
+++ b/src/Tests/PageManagerTranslationIntegrationTest.php
@@ -67,8 +67,13 @@ public function testNode() {
       'path' => 'node/%',
     ];
     $this->drupalPostForm('admin/structure/page_manager/add', $edit, 'Save');
-    $this->clickLink('Edit');
-    $this->drupalPostForm(NULL, ['display_variant[status_code]' => 200], 'Update display variant');
+
+    // Create a new variant.
+    $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->drupalGet('node/' . $node->id());
     $this->assertResponse(200);
diff --git a/src/Tests/PageNodeSelectionTest.php b/src/Tests/PageNodeSelectionTest.php
index e6d3afb..658e59a 100644
--- a/src/Tests/PageNodeSelectionTest.php
+++ b/src/Tests/PageNodeSelectionTest.php
@@ -7,10 +7,11 @@
 
 namespace Drupal\page_manager\Tests;
 
+use Drupal\page_manager\Entity\Page;
 use Drupal\simpletest\WebTestBase;
 
 /**
- * Tests selecting display variants based on nodes.
+ * Tests selecting variants based on nodes.
  *
  * @group page_manager
  */
@@ -59,7 +60,14 @@ public function testAdmin() {
       'path' => 'node/%',
     ];
     $this->drupalPostForm('admin/structure/page_manager/add', $edit, 'Save');
-    // Their pages should now use the default 404 display variant.
+
+    // Create a new variant to always return 404.
+    $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->drupalGet('node/' . $node1->id());
     $this->assertResponse(404);
     $this->assertCacheTag('page_manager_route_name:entity.node.canonical');
@@ -68,14 +76,15 @@ public function testAdmin() {
     $this->assertResponse(404);
     $this->assertNoText($node2->label());
 
-    // Add a new display variant.
+    // Add a new variant.
     $this->drupalGet('admin/structure/page_manager/manage/node_view');
-    $this->clickLink('Add new display variant');
+    $this->clickLink('Add new variant');
     $this->clickLink('Block page');
     $edit = [
-      'display_variant[label]' => 'First',
+      'id' => 'block_page_first',
+      'label' => 'First',
     ];
-    $this->drupalPostForm(NULL, $edit, 'Add display variant');
+    $this->drupalPostForm(NULL, $edit, 'Save');
 
     // Add the entity view block.
     $this->clickLink('Add new block');
@@ -96,9 +105,22 @@ public function testAdmin() {
 
     // Set the page title to the node title.
     $edit = [
-      'display_variant[page_title]' => '[node:title]',
+      'variant_settings[page_title]' => '[node:title]',
     ];
-    $this->drupalPostForm(NULL, $edit, 'Update display variant');
+    $this->drupalPostForm(NULL, $edit, 'Save');
+
+    /** @var \Drupal\page_manager\PageInterface $page */
+    $page = Page::load('node_view');
+    foreach ($page->getVariants() as $block_variant_uuid => $block_variant) {
+      if ($block_variant->label() == 'First') {
+        break;
+      }
+    }
+    // Set the weight of the block_page variant to -10.
+    $edit = [
+      'variants[' . $block_variant_uuid . '][weight]' => -10,
+    ];
+    $this->drupalPostForm(NULL, $edit, '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 6f5c937..a94d361 100644
--- a/src/Tests/PagePlaceholderTest.php
+++ b/src/Tests/PagePlaceholderTest.php
@@ -47,8 +47,13 @@ public function testPagePlaceHolder() {
       'path' => '/page-manager-test/%',
     ];
     $this->drupalPostForm('admin/structure/page_manager/add', $edit, 'Save');
-    $this->clickLink('Edit');
-    $this->drupalPostForm(NULL, ['display_variant[status_code]' => 200], 'Update display variant');
+
+    // Create a new variant.
+    $edit = [
+      'id' => 'http_status_code',
+      'variant_settings[status_code]' => 200,
+    ];
+    $this->drupalPostForm('admin/structure/page_manager/manage/placeholder/add/http_status_code', $edit, 'Save');
 
     // 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/Tests/StaticContextTest.php b/src/Tests/StaticContextTest.php
index 08c0916..2e8f8d9 100644
--- a/src/Tests/StaticContextTest.php
+++ b/src/Tests/StaticContextTest.php
@@ -78,15 +78,16 @@ public function testStaticContext() {
       $this->assertText('The ' . $edit['label'] . ' static context has been added.');
     }
 
-    // Add a new display variant.
+    // Add a new variant.
     $this->drupalGet('admin/structure/page_manager/manage/' . $edit_page['id']);
-    $this->clickLink('Add new display variant');
+    $this->clickLink('Add new variant');
     $this->clickLink('Block page');
     $variant_edit = [
-      'display_variant[label]' => 'Static context blocks',
-      'display_variant[page_title]' => 'Static context test page',
+      'label' => 'Static context blocks',
+      'id' => 'block_page',
+      'variant_settings[page_title]' => 'Static context test page',
     ];
-    $this->drupalPostForm(NULL, $variant_edit, 'Add display variant');
+    $this->drupalPostForm(NULL, $variant_edit, 'Save');
 
     // Add a block that renders the node from the first static context.
     $this->clickLink('Add new block');
@@ -152,7 +153,7 @@ public function testStaticContext() {
 
     // Edit the page variant and remove one static context view block.
     $this->clickLink(t('Edit'), 2);
-    // Remove the second static context view block from the display variant.
+    // Remove the second static context view block from the variant.
     $this->clickLink(t('Delete'), 1);
     $this->drupalPostForm(NULL, NULL, t('Delete'));
 
diff --git a/tests/src/Unit/PageBlockDisplayVariantTest.php b/tests/src/Unit/PageBlockDisplayVariantTest.php
index e883a50..b584dc0 100644
--- a/tests/src/Unit/PageBlockDisplayVariantTest.php
+++ b/tests/src/Unit/PageBlockDisplayVariantTest.php
@@ -23,7 +23,7 @@
 use Symfony\Component\DependencyInjection\ContainerBuilder;
 
 /**
- * Tests the block display variant plugin.
+ * Tests the block variant plugin.
  *
  * @coversDefaultClass \Drupal\page_manager\Plugin\DisplayVariant\PageBlockDisplayVariant
  *
@@ -50,7 +50,7 @@ public function testBuildEmptyBlock() {
     $uuid_generator = $this->prophesize(UuidInterface::class);
     $token = $this->prophesize(Token::class);
 
-    $display_variant = new PageBlockDisplayVariant([], '', [], $context_handler->reveal(), $account->reveal(), $uuid_generator->reveal(), $token->reveal());
+    $variant_plugin = new PageBlockDisplayVariant([], '', [], $context_handler->reveal(), $account->reveal(), $uuid_generator->reveal(), $token->reveal());
 
     // Empty block.
     $expected_build = [
@@ -76,7 +76,7 @@ public function testBuildEmptyBlock() {
       ],
     ];
 
-    $build = $display_variant->buildBlock($build);
+    $build = $variant_plugin->buildBlock($build);
 
     // Assert that cacheability metadata is merged.
     $this->assertSame($expected_build, $build);
@@ -142,7 +142,7 @@ public function testBuild() {
     $token = $this->getMockBuilder(Token::class)
       ->disableOriginalConstructor()
       ->getMock();
-    $display_variant = $this->getMockBuilder(PageBlockDisplayVariant::class)
+    $variant_plugin = $this->getMockBuilder(PageBlockDisplayVariant::class)
       ->setConstructorArgs([['page_title' => $page_title, 'uuid' => 'UUID'], 'test', [], $context_handler->reveal(), $account->reveal(), $uuid_generator->reveal(), $token])
       ->setMethods(['getBlockCollection', 'renderPageTitle'])
       ->getMock();
@@ -150,10 +150,10 @@ public function testBuild() {
     $page = $this->prophesize(PageInterface::class);
     $page->id()->willReturn('page_id');
 
-    $display_variant->expects($this->once())
+    $variant_plugin->expects($this->once())
       ->method('getBlockCollection')
       ->willReturn($block_collection);
-    $display_variant->expects($this->once())
+    $variant_plugin->expects($this->once())
       ->method('renderPageTitle')
       ->with($page_title)
       ->willReturn($page_title);
@@ -180,9 +180,9 @@ public function testBuild() {
 
     // Build the variant and ensure that pre_render is set only for the first
     // block.
-    $build = $display_variant->build();
-    $build = $display_variant->buildRegions($build);
-    $this->assertSame([$display_variant, 'buildBlock'], $build['top']['block1']['#pre_render'][0]);
+    $build = $variant_plugin->build();
+    $build = $variant_plugin->buildRegions($build);
+    $this->assertSame([$variant_plugin, 'buildBlock'], $build['top']['block1']['#pre_render'][0]);
     $this->assertTrue(empty($build['top']['block2']));
     $this->assertSame($expected_cache_block1, $build['top']['block1']['#cache']);
     $this->assertSame($expected_cache_page, $build['#cache']);
@@ -191,7 +191,7 @@ public function testBuild() {
     $block1->build()->willReturn([
       '#markup' => 'block1_build_value',
     ]);
-    $block1_build = $display_variant->buildBlock($build['top']['block1']);
+    $block1_build = $variant_plugin->buildBlock($build['top']['block1']);
     $this->assertSame(['#markup' => 'block1_build_value'], $block1_build['content']);
   }
 
@@ -206,17 +206,17 @@ public function testSubmitConfigurationForm() {
     $uuid_generator = $this->prophesize(UuidInterface::class);
     $token = $this->prophesize(Token::class);
 
-    $display_variant = new PageBlockDisplayVariant([], '', [], $context_handler->reveal(), $account->reveal(), $uuid_generator->reveal(), $token->reveal());
+    $variant_plugin = new PageBlockDisplayVariant([], '', [], $context_handler->reveal(), $account->reveal(), $uuid_generator->reveal(), $token->reveal());
 
     $values = ['page_title' => "Go hang a salami, I'm a lasagna hog!"];
 
     $form = [];
     $form_state = (new FormState())->setValues($values);
-    $display_variant->submitConfigurationForm($form, $form_state);
+    $variant_plugin->submitConfigurationForm($form, $form_state);
 
-    $property = new \ReflectionProperty($display_variant, 'configuration');
+    $property = new \ReflectionProperty($variant_plugin, 'configuration');
     $property->setAccessible(TRUE);
-    $this->assertSame($values['page_title'], $property->getValue($display_variant)['page_title']);
+    $this->assertSame($values['page_title'], $property->getValue($variant_plugin)['page_title']);
   }
 
 }
diff --git a/tests/src/Unit/PageExecutableTest.php b/tests/src/Unit/PageExecutableTest.php
index 739452c..4bcb877 100644
--- a/tests/src/Unit/PageExecutableTest.php
+++ b/tests/src/Unit/PageExecutableTest.php
@@ -14,6 +14,7 @@
 use Drupal\page_manager\Event\PageManagerEvents;
 use Drupal\page_manager\PageExecutable;
 use Drupal\page_manager\PageInterface;
+use Drupal\page_manager\PageVariantInterface;
 use Drupal\Tests\UnitTestCase;
 use Prophecy\Argument;
 use Symfony\Component\EventDispatcher\EventDispatcherInterface;
diff --git a/tests/src/Unit/VariantAwareTraitTest.php b/tests/src/Unit/VariantAwareTraitTest.php
deleted file mode 100644
index bacdd59..0000000
--- a/tests/src/Unit/VariantAwareTraitTest.php
+++ /dev/null
@@ -1,214 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\Tests\page_manager\Unit\VariantAwareTraitTest.
- */
-
-namespace Drupal\Tests\page_manager\Unit;
-
-use Drupal\Component\Plugin\PluginManagerInterface;
-use Drupal\Component\Uuid\UuidInterface;
-use Drupal\Core\DependencyInjection\ContainerBuilder;
-use Drupal\Core\Display\VariantInterface;
-use Drupal\page_manager\Plugin\VariantAwareTrait;
-use Drupal\page_manager\Plugin\VariantCollection;
-use Drupal\Tests\UnitTestCase;
-use Prophecy\Argument;
-
-/**
- * Tests the methods of a variant-aware class.
- *
- * @coversDefaultClass \Drupal\page_manager\Plugin\VariantAwareTrait
- *
- * @group PageManager
- */
-class VariantAwareTraitTest extends UnitTestCase {
-
-  /**
-   * @var \Drupal\Component\Plugin\PluginManagerInterface|\PHPUnit_Framework_MockObject_MockObject
-   */
-  protected $manager;
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function setUp() {
-    parent::setUp();
-    $container = new ContainerBuilder();
-    $this->manager = $this->prophesize(PluginManagerInterface::class);
-    $container->set('plugin.manager.display_variant', $this->manager->reveal());
-    \Drupal::setContainer($container);
-  }
-
-  /**
-   * @covers ::getVariants
-   */
-  public function testGetVariantsEmpty() {
-    $trait_object = new TestVariantAwareTrait();
-    $this->manager->createInstance()->shouldNotBeCalled();
-
-    $variants = $trait_object->getVariants();
-    $this->assertInstanceOf(VariantCollection::class, $variants);
-    $this->assertSame(0, count($variants));
-  }
-
-  /**
-   * @covers ::getVariants
-   */
-  public function testGetVariants() {
-    $trait_object = new TestVariantAwareTrait();
-    $config = [
-      'foo' => ['id' => 'foo_plugin'],
-      'bar' => ['id' => 'bar_plugin'],
-    ];
-    foreach ($config as $value) {
-      $plugin = $this->prophesize(VariantInterface::class);
-      $this->manager->createInstance($value['id'], $value)->willReturn($plugin->reveal());
-    }
-    $trait_object->setVariantConfig($config);
-
-    $variants = $trait_object->getVariants();
-    $this->assertInstanceOf(VariantCollection::class, $variants);
-    $this->assertSame(2, count($variants));
-    return $variants;
-  }
-
-  /**
-   * @covers ::getVariants
-   *
-   * @depends testGetVariants
-   */
-  public function testGetVariantsSort(VariantCollection $variants) {
-    $this->assertSame(['bar' => 'bar', 'foo' => 'foo'], $variants->getInstanceIds());
-  }
-
-  /**
-   * @covers ::addVariant
-   */
-  public function testAddVariant() {
-    $config = ['id' => 'foo'];
-    $uuid = 'test-uuid';
-    $expected_config = $config + ['uuid' => $uuid];
-
-    $uuid_generator = $this->prophesize(UuidInterface::class);
-    $uuid_generator->generate()
-      ->willReturn($uuid)
-      ->shouldBeCalledTimes(1);
-    $trait_object = new TestVariantAwareTrait();
-    $trait_object->setUuidGenerator($uuid_generator->reveal());
-
-    $plugin_prophecy = $this->prophesize(VariantInterface::class);
-    $plugin_prophecy->getConfiguration()
-      ->willReturn($expected_config)
-      ->shouldBeCalled();
-    $plugin_prophecy->setConfiguration($expected_config)
-      ->willReturn($expected_config)
-      ->shouldBeCalled();
-
-    $this->manager->createInstance('foo', $expected_config)
-      ->willReturn($plugin_prophecy->reveal());
-
-    $resulting_uuid = $trait_object->addVariant($config);
-    $this->assertSame($uuid, $resulting_uuid);
-
-    $variants = $trait_object->getVariants();
-    $this->assertSame([$uuid => $uuid], $variants->getInstanceIds());
-    $this->assertSame([$uuid => $expected_config], $variants->getConfiguration());
-    $this->assertSame($plugin_prophecy->reveal(), $variants->get($uuid));
-    return [$trait_object, $uuid, $plugin_prophecy->reveal()];
-  }
-
-  /**
-   * @covers ::getVariant
-   *
-   * @depends testAddVariant
-   */
-  public function testGetVariant($data) {
-    list($trait_object, $uuid, $plugin) = $data;
-    $this->manager->createInstance()->shouldNotBeCalled();
-
-    $this->assertSame($plugin, $trait_object->getVariant($uuid));
-    return [$trait_object, $uuid];
-  }
-
-  /**
-   * @covers ::removeVariant
-   *
-   * @depends testGetVariant
-   */
-  public function testRemoveVariant($data) {
-    list($trait_object, $uuid) = $data;
-
-    $this->assertSame($trait_object, $trait_object->removeVariant($uuid));
-    $this->assertFalse($trait_object->getVariants()->has($uuid));
-    return [$trait_object, $uuid];
-  }
-
-  /**
-   * @covers ::getVariant
-   *
-   * @depends testRemoveVariant
-   *
-   * @expectedException \Drupal\Component\Plugin\Exception\PluginNotFoundException
-   * @expectedExceptionMessage Plugin ID 'test-uuid' was not found.
-   */
-  public function testGetVariantException($data) {
-    list($trait_object, $uuid) = $data;
-    // Attempt to retrieve a variant that has been removed.
-    $this->assertNull($trait_object->getVariant($uuid));
-  }
-
-}
-
-class TestVariantAwareTrait {
-  use VariantAwareTrait;
-
-  /**
-   * @var array
-   */
-  protected $variantConfig = [];
-
-  /**
-   * @var \Drupal\Component\Uuid\UuidInterface
-   */
-  protected $uuidGenerator;
-
-  /**
-   * @param \Drupal\Component\Uuid\UuidInterface $uuid_generator
-   *
-   * @return $this
-   */
-  public function setUuidGenerator(UuidInterface $uuid_generator) {
-    $this->uuidGenerator = $uuid_generator;
-    return $this;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function uuidGenerator() {
-    return $this->uuidGenerator;
-  }
-
-  /**
-   * Sets the variant configuration.
-   *
-   * @param array $config
-   *   The variant configuration.
-   *
-   * @return $this
-   */
-  public function setVariantConfig(array $config) {
-    $this->variantConfig = $config;
-    return $this;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function getVariantConfig() {
-    return $this->variantConfig;
-  }
-
-}
diff --git a/tests/src/Unit/VariantRouteFilterTest.php b/tests/src/Unit/VariantRouteFilterTest.php
index 34e2842..01f3f46 100644
--- a/tests/src/Unit/VariantRouteFilterTest.php
+++ b/tests/src/Unit/VariantRouteFilterTest.php
@@ -15,6 +15,7 @@
 use Drupal\Core\Path\CurrentPathStack;
 use Drupal\page_manager\PageExecutableInterface;
 use Drupal\page_manager\PageInterface;
+use Drupal\page_manager\PageVariantInterface;
 use Drupal\page_manager\Routing\VariantRouteFilter;
 use Drupal\Tests\UnitTestCase;
 use Symfony\Cmf\Component\Routing\Enhancer\RouteEnhancerInterface;
@@ -114,11 +115,11 @@ public function testFilterContextException() {
     $route = new Route('/path/with/{slug}', ['page_manager_page' => 'a_page', 'variant_id' => 'a_variant']);
     $route_collection->add('a_route', $route);
 
-    $variant = $this->prophesize(VariantInterface::class);
-    $variant->access()->willThrow(new ContextException());
+    $page_variant = $this->prophesize(PageVariantInterface::class);
+    $page_variant->canSelect()->willThrow(new ContextException());
 
     $executable = $this->prophesize(PageExecutableInterface::class);
-    $executable->getRuntimeVariant('a_variant')->willReturn($variant);
+    $executable->getRuntimeVariant('a_variant')->willReturn($page_variant);
 
     $page = $this->prophesize(PageInterface::class);
     $page->getExecutable()->willReturn($executable->reveal());
@@ -162,11 +163,11 @@ public function testFilterDeniedAccess() {
     $route = new Route('/path/with/{slug}', ['page_manager_page' => 'a_page', 'variant_id' => 'a_variant']);
     $route_collection->add('a_route', $route);
 
-    $variant = $this->prophesize(VariantInterface::class);
-    $variant->access()->willReturn(FALSE);
+    $page_variant = $this->prophesize(PageVariantInterface::class);
+    $page_variant->canSelect()->willReturn(FALSE);
 
     $executable = $this->prophesize(PageExecutableInterface::class);
-    $executable->getRuntimeVariant('a_variant')->willReturn($variant);
+    $executable->getRuntimeVariant('a_variant')->willReturn($page_variant);
 
     $page = $this->prophesize(PageInterface::class);
     $page->getExecutable()->willReturn($executable->reveal());
@@ -191,11 +192,11 @@ public function testFilterAllowedAccess() {
     $route = new Route('/path/with/{slug}', ['page_manager_page' => 'a_page', 'variant_id' => 'a_variant']);
     $route_collection->add('a_route', $route);
 
-    $variant = $this->prophesize(ContextAwareVariantInterface::class);
-    $variant->access()->willReturn(TRUE);
+    $page_variant = $this->prophesize(PageVariantInterface::class);
+    $page_variant->canSelect()->willReturn(TRUE);
 
     $executable = $this->prophesize(PageExecutableInterface::class);
-    $executable->getRuntimeVariant('a_variant')->willReturn($variant);
+    $executable->getRuntimeVariant('a_variant')->willReturn($page_variant);
 
     $page = $this->prophesize(PageInterface::class);
     $page->getExecutable()->willReturn($executable->reveal());
@@ -222,11 +223,11 @@ public function testFilterAllowedAccessTwoRoutes() {
     $route_collection->add('route_1', $route1);
     $route_collection->add('route_2', $route2);
 
-    $variant = $this->prophesize(ContextAwareVariantInterface::class);
-    $variant->access()->willReturn(TRUE);
+    $page_variant = $this->prophesize(PageVariantInterface::class);
+    $page_variant->canSelect()->willReturn(TRUE);
 
     $executable = $this->prophesize(PageExecutableInterface::class);
-    $executable->getRuntimeVariant('variant_1')->willReturn($variant);
+    $executable->getRuntimeVariant('variant_1')->willReturn($page_variant);
     $executable->getRuntimeVariant('variant_2')->shouldNotBeCalled();
 
     $page = $this->prophesize(PageInterface::class);
@@ -255,14 +256,14 @@ public function testFilterAllowedAccessSecondRoute() {
     $route_collection->add('route_1', $route1);
     $route_collection->add('route_2', $route2);
 
-    $variant1 = $this->prophesize(VariantInterface::class);
-    $variant1->access()->willReturn(FALSE);
-    $variant2 = $this->prophesize(VariantInterface::class);
-    $variant2->access()->willReturn(TRUE);
+    $page_variant1 = $this->prophesize(PageVariantInterface::class);
+    $page_variant1->canSelect()->willReturn(FALSE);
+    $page_variant2 = $this->prophesize(PageVariantInterface::class);
+    $page_variant2->canSelect()->willReturn(TRUE);
 
     $executable = $this->prophesize(PageExecutableInterface::class);
-    $executable->getRuntimeVariant('variant_1')->willReturn($variant1);
-    $executable->getRuntimeVariant('variant_2')->willReturn($variant2);
+    $executable->getRuntimeVariant('variant_1')->willReturn($page_variant1);
+    $executable->getRuntimeVariant('variant_2')->willReturn($page_variant2);
 
     $page1 = $this->prophesize(PageInterface::class);
     $page1->getExecutable()->willReturn($executable->reveal());
@@ -300,14 +301,14 @@ public function testFilterAllowedAccessFirstRoute() {
     $route_collection->add('route_3', $route3);
     $route_collection->add('route_4', $route4);
 
-    $variant1 = $this->prophesize(VariantInterface::class);
-    $variant1->access()->willReturn(TRUE);
-    $variant2 = $this->prophesize(VariantInterface::class);
-    $variant2->access()->willReturn(FALSE);
+    $page_variant1 = $this->prophesize(PageVariantInterface::class);
+    $page_variant1->canSelect()->willReturn(TRUE);
+    $page_variant2 = $this->prophesize(PageVariantInterface::class);
+    $page_variant2->canSelect()->willReturn(FALSE);
 
     $executable = $this->prophesize(PageExecutableInterface::class);
-    $executable->getRuntimeVariant('variant1')->willReturn($variant1);
-    $executable->getRuntimeVariant('variant2')->willReturn($variant2);
+    $executable->getRuntimeVariant('variant1')->willReturn($page_variant1);
+    $executable->getRuntimeVariant('variant2')->willReturn($page_variant2);
 
     $page = $this->prophesize(PageInterface::class);
     $page->getExecutable()->willReturn($executable->reveal());
@@ -331,11 +332,11 @@ public function testFilterRequestAttributes() {
     $route = new Route('/path/with/{slug}', ['page_manager_page' => 'a_page', 'variant_id' => 'a_variant']);
     $route_collection->add('a_route', $route);
 
-    $variant = $this->prophesize(ContextAwareVariantInterface::class);
-    $variant->access()->willReturn(TRUE);
+    $page_variant = $this->prophesize(PageVariantInterface::class);
+    $page_variant->canSelect()->willReturn(TRUE);
 
     $executable = $this->prophesize(PageExecutableInterface::class);
-    $executable->getRuntimeVariant('a_variant')->willReturn($variant);
+    $executable->getRuntimeVariant('a_variant')->willReturn($page_variant);
 
     $page = $this->prophesize(PageInterface::class);
     $page->getExecutable()->willReturn($executable->reveal());
