diff --git a/includes/yamlform.theme.inc b/includes/yamlform.theme.inc
index f923099..843d6bd 100644
--- a/includes/yamlform.theme.inc
+++ b/includes/yamlform.theme.inc
@@ -136,7 +136,7 @@ function template_preprocess_yamlform_confirmation(array &$variables) {
   $settings = $yamlform->getSettings();
 
   // Set progress.
-  if ($settings['wizard_complete'] && ($settings['wizard_progress_bar'] || $settings['wizard_progress_pages'] || $settings['wizard_progress_percentage'])) {
+  if ($yamlform->getPages() && $settings['wizard_complete'] && ($settings['wizard_progress_bar'] || $settings['wizard_progress_pages'] || $settings['wizard_progress_percentage'])) {
     $variables['progress'] = [
       '#theme' => 'yamlform_progress',
       '#yamlform' => $yamlform,
@@ -491,15 +491,15 @@ function template_preprocess_yamlform_container_base_text(array &$variables) {
 function template_preprocess_yamlform_progress(array &$variables) {
   /** @var \Drupal\yamlform\YamlFormInterface $yamlform */
   $yamlform = $variables['yamlform'];
+  $current_page = $variables['current_page'];
 
   $pages = $yamlform->getPages();
 
-  $total = count($pages);
-  $current_page = $variables['current_page'];
-  // Jump to last page if 'current_page' is complete.
-  if ($current_page === 'complete') {
-    $current_page = $total - 1;
-  }
+  $page_keys = array_keys($pages);
+  $page_indexes = array_flip($page_keys);
+  $current_index = $page_indexes[$current_page];
+
+  $total = count($page_keys);
 
   if ($yamlform->getSetting('wizard_progress_bar')) {
     $variables['bar'] = [
@@ -510,11 +510,11 @@ function template_preprocess_yamlform_progress(array &$variables) {
   }
 
   if ($yamlform->getSetting('wizard_progress_pages')) {
-    $variables['pages'] = t('Page @start of @end', ['@start' => $current_page + 1, '@end' => $total]);
+    $variables['summary'] = t('Page @start of @end', ['@start' => $current_index  + 1, '@end' => $total]);
   }
 
   if ($yamlform->getSetting('wizard_progress_percentage')) {
-    $variables['percentage'] = number_format(($current_page / ($total - 1)) * 100, 0) . '%';
+    $variables['percentage'] = number_format(($current_index / ($total - 1)) * 100, 0) . '%';
   }
 }
 
@@ -531,16 +531,19 @@ function template_preprocess_yamlform_progress(array &$variables) {
 function template_preprocess_yamlform_progress_bar(array &$variables) {
   /** @var \Drupal\yamlform\YamlFormInterface $yamlform */
   $yamlform = $variables['yamlform'];
+  $current_page = $variables['current_page'];
 
   $pages = $yamlform->getPages();
-  $variables['pages'] = [];
-  foreach ($pages as $page) {
-    $variables['pages'][] = (isset($page['#title'])) ? $page['#title'] : '';
-  }
 
-  // Jump to last page if 'current_page' is complete.
-  if ($variables['current_page'] && $variables['current_page'] === 'complete') {
-    $variables['current_page'] = count($variables['pages']) - 1;
+  $page_keys = array_keys($pages);
+  $page_indexes = array_flip($page_keys);
+  $current_index = $page_indexes[$current_page];
+  $variables['current_index'] = $current_index;
+
+  // Reset the pages variable.
+  $variables['progress'] = [];
+  foreach ($pages as $page) {
+    $variables['progress'][] = (isset($page['#title'])) ? $page['#title'] : '';
   }
 }
 
diff --git a/src/Entity/YamlForm.php b/src/Entity/YamlForm.php
index 8e3dca3..2a4a418 100644
--- a/src/Entity/YamlForm.php
+++ b/src/Entity/YamlForm.php
@@ -11,6 +11,7 @@ use Drupal\Core\Session\AccountInterface;
 use Drupal\Core\StringTranslation\StringTranslationTrait;
 use Drupal\user\Entity\User;
 use Drupal\user\UserInterface;
+use Drupal\yamlform\Utility\YamlFormArrayHelper;
 use Drupal\yamlform\Utility\YamlFormElementHelper;
 use Drupal\yamlform\YamlFormHandlerInterface;
 use Drupal\yamlform\YamlFormHandlerPluginCollection;
@@ -1051,37 +1052,45 @@ class YamlForm extends ConfigEntityBundleBase implements YamlFormInterface {
    * {@inheritdoc}
    */
   public function getPages() {
-    if (!isset($this->pages)) {
-      // Add form page containers.
-      $this->pages = [];
-      $elements = $this->getElementsInitialized();
+    if (isset($this->pages)) {
+      return $this->pages;
+    }
+
+    $wizard_properties = [
+      '#title' => '#title',
+      '#prev_button_label' => '#prev_button_label',
+      '#next_button_label' => '#next_button_label',
+    ];
+
+    $elements = $this->getElementsInitialized();
+
+    // Add form page containers.
+    $this->pages = [];
+    if (is_array($elements)) {
       foreach ($elements as $key => $element) {
         if (isset($element['#type']) && $element['#type'] == 'yamlform_wizard_page') {
-          $this->pages[$key] = $element;
+          $this->pages[$key] = array_intersect_key($element, $wizard_properties);
         }
       }
+    }
 
-      // Add preview page.
-      $settings = $this->getSettings();
-      if ($settings['preview'] != DRUPAL_DISABLED) {
-        // If there is no start page, we must define one.
-        if (empty($this->pages)) {
-          $this->pages['start'] = [
-            '#type' => 'yamlform_wizard_page',
-            '#title' => $this->getSetting('wizard_start_label') ?: \Drupal::config('yamlform.settings')->get('settings.default_wizard_start_label'),
-          ];
-        }
-        $this->pages['preview'] = [
-          '#type' => 'yamlform_preview',
-          '#title' => $this->t('Preview'),
+    // Add preview page.
+    $settings = $this->getSettings();
+    if ($settings['preview'] != DRUPAL_DISABLED) {
+      // If there is no start page, we must define one.
+      if (empty($this->pages)) {
+        $this->pages['start'] = [
+          '#title' => $this->getSetting('wizard_start_label') ?: \Drupal::config('yamlform.settings')->get('settings.default_wizard_start_label'),
         ];
       }
+      $this->pages['preview'] = [
+        '#title' => $this->t('Preview'),
+      ];
     }
 
     // Only add complete page, if there are some pages.
-    if ($this->pages  && $this->getSetting('wizard_complete')) {
+    if ($this->pages && $this->getSetting('wizard_complete')) {
       $this->pages['complete'] = [
-        '#type' => 'yamlform_wizard_page',
         '#title' => $this->getSetting('wizard_complete_label') ?: \Drupal::config('yamlform.settings')->get('settings.default_wizard_complete_label'),
       ];
     }
@@ -1092,18 +1101,9 @@ class YamlForm extends ConfigEntityBundleBase implements YamlFormInterface {
   /**
    * {@inheritdoc}
    */
-  public function getPage($index) {
+  public function getPage($key) {
     $pages = $this->getPages();
-    if (isset($pages[$index])) {
-      return $pages[$index];
-    }
-
-    $keys = array_keys($pages);
-    if (isset($keys[$index])) {
-      return $pages[$keys[$index]];
-    }
-
-    return NULL;
+    return (isset($pages[$key])) ? $pages[$key] : NULL;
   }
 
   /**
diff --git a/src/Tests/YamlFormHandlerTest.php b/src/Tests/YamlFormHandlerTest.php
index 6bb33da..7092032 100644
--- a/src/Tests/YamlFormHandlerTest.php
+++ b/src/Tests/YamlFormHandlerTest.php
@@ -92,7 +92,7 @@ class YamlFormHandlerTest extends YamlFormTestBase {
     $this->assertRaw('Invoked: Drupal\yamlform_test\Plugin\YamlFormHandler\TestYamlFormHandler:preCreate');
     $this->assertRaw('Invoked: Drupal\yamlform_test\Plugin\YamlFormHandler\TestYamlFormHandler:postCreate');
     $this->assertRaw('Invoked: Drupal\yamlform_test\Plugin\YamlFormHandler\TestYamlFormHandler:alterElements');
-    $this->assertNoRaw('Invoked: Drupal\yamlform_test\Plugin\YamlFormHandler\TestYamlFormHandler:alterForm');
+    $this->assertRaw('Invoked: Drupal\yamlform_test\Plugin\YamlFormHandler\TestYamlFormHandler:alterForm');
 
     // Check admin can still post submission.
     $this->drupalLogin($this->adminFormUser);
diff --git a/src/Tests/YamlFormWizardTest.php b/src/Tests/YamlFormWizardTest.php
index 8afb534..856cea5 100644
--- a/src/Tests/YamlFormWizardTest.php
+++ b/src/Tests/YamlFormWizardTest.php
@@ -38,9 +38,53 @@ class YamlFormWizardTest extends WebTestBase {
   }
 
   /**
-   * Test form wizard.
+   * Test form custom wizard, advanced wizard, and custom wizard settings.
    */
   public function testWizard() {
+
+    /* Custom wizard */
+
+    // Check current page is #1.
+    $this->drupalGet('yamlform/test_form_wizard_custom');
+    $this->assertCurrentPage('Wizard page #1');
+
+    // Check next page is #2.
+    $this->drupalPostForm('yamlform/test_form_wizard_custom', [], 'Next Page >');
+    $this->assertCurrentPage('Wizard page #2');
+
+    // Check previous page is #1.
+    $this->drupalPostForm(NULL, [], '< Previous Page');
+    $this->assertCurrentPage('Wizard page #1');
+
+    // Hide pages #3 and #4.
+    $edit = [
+      'pages[wizard_1]' => TRUE,
+      'pages[wizard_2]' => TRUE,
+      'pages[wizard_3]' => FALSE,
+      'pages[wizard_4]' => FALSE,
+      'pages[wizard_5]' => TRUE,
+      'pages[wizard_6]' => TRUE,
+      'pages[wizard_7]' => TRUE,
+    ];
+    $this->drupalPostForm(NULL, $edit, 'Next Page >');
+
+    // Check next page is #2.
+    $this->assertCurrentPage('Wizard page #2');
+
+    // Check next page is #5.
+    $this->drupalPostForm(NULL, [], 'Next Page >');
+    $this->assertCurrentPage('Wizard page #5');
+
+    // Check previous page is #2.
+    $this->drupalPostForm(NULL, [], '< Previous Page');
+    $this->assertCurrentPage('Wizard page #2');
+
+    // Check previous page is #1.
+    $this->drupalPostForm(NULL, [], '< Previous Page');
+    $this->assertCurrentPage('Wizard page #1');
+
+    /* Advanced wizard */
+
     $yamlform_wizard_advanced = YamlForm::load('test_form_wizard_advanced');
 
     // Get initial wizard start page (Your Information).
@@ -123,7 +167,7 @@ class YamlFormWizardTest extends WebTestBase {
     // Check nosave attributes.
     $this->assertRaw('data-yamlform-unsaved');
     // Check progress bar is set to 'Contact Information'.
-    $this->assertPattern('#<li class="yamlform-progress-bar__page yamlform-progress-bar__page--current">\s+<b>Contact Information</b></li>#');
+    $this->assertCurrentPage('Contact Information');
     // Check progress pages.
     $this->assertRaw('Page 2 of 5');
     // Check progress percentage.
@@ -140,12 +184,12 @@ class YamlFormWizardTest extends WebTestBase {
     // Complete reload the form.
     $this->drupalGet('yamlform/test_form_wizard_advanced');
     // Check progress bar is still set to 'Contact Information'.
-    $this->assertPattern('#<li class="yamlform-progress-bar__page yamlform-progress-bar__page--current">\s+<b>Contact Information</b></li>#');
+    $this->assertCurrentPage('Contact Information');
 
     // Move to last page (Your Feedback).
     $this->drupalPostForm(NULL, [], t('Next Page >'));
     // Check progress bar is set to 'Your Feedback'.
-    $this->assertPattern('#<li class="yamlform-progress-bar__page yamlform-progress-bar__page--current">\s+<b>Your Feedback</b></li>#');
+    $this->assertCurrentPage('Your Feedback');
     // Check previous button does exist.
     $this->assertFieldById('edit-previous', '< Previous Page');
     // Check next button is labeled 'Preview'.
@@ -159,7 +203,7 @@ class YamlFormWizardTest extends WebTestBase {
     ];
     $this->drupalPostForm(NULL, $edit, t('Preview'));
     // Check progress bar is set to 'Preview'.
-    $this->assertPattern('#<li class="yamlform-progress-bar__page yamlform-progress-bar__page--current">\s+<b>Preview</b></li>#');
+    $this->assertCurrentPage('Preview');
     // Check progress pages.
     $this->assertRaw('Page 4 of 5');
     // Check progress percentage.
@@ -174,14 +218,15 @@ class YamlFormWizardTest extends WebTestBase {
 
     // Submit the form.
     $this->drupalPostForm(NULL, [], t('Submit'));
-    // Check progress bar is set to 'Completed'.
-    $this->assertPattern('#<li class="yamlform-progress-bar__page yamlform-progress-bar__page--current">\s+<b>Complete</b><span></span></li>#');
+    // Check progress bar is set to 'Complete'.
+    $this->assertCurrentPage('Complete');
     // Check progress pages.
     $this->assertRaw('Page 5 of 5');
     // Check progress percentage.
     $this->assertRaw('(100%)');
 
-    /* Custom wizard settings */
+    /* Custom wizard settings (using advanced wizard) */
+
     $this->drupalLogout();
 
     // Check global next and previous button labels.
@@ -276,4 +321,14 @@ class YamlFormWizardTest extends WebTestBase {
     $this->assertNoRaw('class="yamlform-progress-bar"');
   }
 
+  /**
+   * Assert the current page using the progress bar's markup.
+   *
+   * @param string $title
+   *   The title of the current page.
+   */
+  protected function assertCurrentPage($title) {
+    $this->assertPattern('|<li class="yamlform-progress-bar__page yamlform-progress-bar__page--current">\s+<b>' . $title . '</b>|');
+  }
+
 }
diff --git a/src/Utility/YamlFormArrayHelper.php b/src/Utility/YamlFormArrayHelper.php
index 23aba6b..f89793a 100644
--- a/src/Utility/YamlFormArrayHelper.php
+++ b/src/Utility/YamlFormArrayHelper.php
@@ -85,4 +85,88 @@ class YamlFormArrayHelper {
     return !self::isAssociative($array);
   }
 
+  /**
+   * Get the first key in an array.
+   *
+   * @param array $array
+   *   An array.
+   *
+   * @return string|null
+   *   The first key in an array.
+   */
+  public static function getFirstKey(array $array) {
+    $keys = array_keys($array);
+    return reset($keys);
+  }
+
+  /**
+   * Get the last key in an array.
+   *
+   * @param array $array
+   *   An array.
+   *
+   * @return string|null
+   *   The last key in an array.
+   */
+  public static function getLastKey(array $array) {
+    $keys = array_keys($array);
+    return end($keys);
+  }
+
+  /**
+   * Get the next key in an array.
+   *
+   * @param array $array
+   *   An array.
+   * @param string $key
+   *   A key.
+   *
+   * @return string|null
+   *   The next key in an array or NULL is there is no next key.
+   */
+  public static function getNextKey(array $array, $key) {
+    return self::getKey($array, $key, 'next');
+  }
+
+  /**
+   * Get the prev(ious) key in an array.
+   *
+   * @param array $array
+   *   An array.
+   * @param string $key
+   *   A key.
+   *
+   * @return string|null
+   *   The prev(ious) key in an array or NULL is there is no previous key.
+   */
+  public static function getPreviousKey(array $array, $key) {
+    return self::getKey($array, $key, 'prev');
+  }
+
+  /**
+   * Get next or prev(ious) page key.
+   *
+   * @param $key
+   *   A page key.
+   * @param string $direction
+   *   The direction of the page key to retrieve
+   * @param bool $visible
+   *   If TRUE only visible pages will be returned.  Defaults to FALSE.
+   *
+   * @return string|null
+   *   The next or prev(ious) page key or NULL if no key is found.
+   *
+   * @see http://stackoverflow.com/questions/6407795/get-the-next-array-item-using-the-key-php
+   */
+  protected static function getKey(array $array, $key, $direction) {
+    $array_keys = array_keys($array);
+    $array_key = reset($array_keys);
+    do {
+      if ($array_key == $key) {
+        return $direction($array_keys);
+      }
+    } while ($array_key = next($array_keys));
+    return NULL;
+  }
+
 }
diff --git a/src/YamlFormInterface.php b/src/YamlFormInterface.php
index ace0654..f1a5adb 100644
--- a/src/YamlFormInterface.php
+++ b/src/YamlFormInterface.php
@@ -375,13 +375,13 @@ interface YamlFormInterface extends ConfigEntityInterface, EntityWithPluginColle
   /**
    * Get form wizard page.
    *
-   * @param string|int $index
-   *   The name or index of a form wizard page.
+   * @param string|int $key
+   *   The name/key of a form wizard page.
    *
    * @return array|null
    *   A form wizard page element.
    */
-  public function getPage($index);
+  public function getPage($key);
 
   /**
    * Update submit and confirm paths (ie URL aliases) associated with this form.
diff --git a/src/YamlFormSubmissionForm.php b/src/YamlFormSubmissionForm.php
index 2bc1cbd..e902acb 100644
--- a/src/YamlFormSubmissionForm.php
+++ b/src/YamlFormSubmissionForm.php
@@ -13,6 +13,7 @@ use Drupal\Core\Render\Element;
 use Drupal\Core\Routing\TrustedRedirectResponse;
 use Drupal\Core\Url;
 use Drupal\yamlform\Controller\YamlFormController;
+use Drupal\yamlform\Utility\YamlFormArrayHelper;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -142,6 +143,7 @@ class YamlFormSubmissionForm extends ContentEntityForm {
   public function buildForm(array $form, FormStateInterface $form_state) {
     /* @var $yamlform_submission \Drupal\yamlform\YamlFormSubmissionInterface */
     $yamlform_submission = $this->getEntity();
+    $yamlform = $this->getYamlForm();
 
     // This submission form is based on the current URL, and hence it depends
     // on the 'url' cache context.
@@ -159,43 +161,12 @@ class YamlFormSubmissionForm extends ContentEntityForm {
     // Build the form.
     $form = parent::buildForm($form, $form_state);
 
-    // Get wizard.
-    $wizard = $form_state->get('wizard');
-
-    // Add unsaved message.
-    if ($this->getYamlFormSetting('form_unsaved')) {
-      $form['#attributes']['class'][] = 'js-yamlform-unsaved';
-      $form['#attached']['library'][] = 'yamlform/yamlform.form.unsaved';
-      if ($wizard['current']) {
-        $form['#attributes']['data-yamlform-unsaved'] = TRUE;
-      }
-    }
-
-    // Add novalidate attribute to form if client side validation disabled.
-    if ($this->getYamlFormSetting('form_novalidate')) {
-      $form['#attributes']['novalidate'] = 'novalidate';
-    }
-
-    // Display collapse/expand all details link.
-    if ($this->getYamlFormSetting('form_details_toggle')) {
-      $form['#attributes']['class'][] = 'yamlform-details-toggle';
-      $form['#attached']['library'][] = 'yamlform/yamlform.element.details.toggle';
-    }
-
-    // Add autofocus class to form.
-    if ($this->entity->isNew() && $this->getYamlFormSetting('form_autofocus')) {
-      $form['#attributes']['class'][] = 'js-yamlform-autofocus';
-    }
-
-    // Disable form auto submit on enter for wizard forms only.
-    if ($wizard['total']) {
-      $form['#attributes']['class'][] = 'js-yamlform-disable-autosubmit';
-    }
+    // Alter form via form handler.
+    $this->getYamlForm()->invokeHandlers('alterForm', $form, $form_state, $yamlform_submission);
 
     // Call custom form alter hook.
     $form_id = $this->getFormId();
     $this->thirdPartySettingsManager->alter('yamlform_submission_form', $form, $form_state, $form_id);
-
     return $form;
   }
 
@@ -256,32 +227,24 @@ class YamlFormSubmissionForm extends ContentEntityForm {
     // Move all $elements properties to the $form.
     $this->setFormPropertiesFromElements($form, $elements);
 
-    // Init wizard.
-    $this->initFormWizardState($form, $form_state);
-
     // Add wizard progress tracker to the form.
-    if ($this->getYamlFormSetting('wizard_progress_bar') || $this->getYamlFormSetting('wizard_progress_pages') || $this->getYamlFormSetting('wizard_progress_percentage')) {
-      $wizard = $form_state->get('wizard');
-      if ($wizard['total']) {
-        $form['progress'] = [
-          '#theme' => 'yamlform_progress',
-          '#yamlform' => $this->getYamlForm(),
-          '#current_page' => $wizard['current'],
-        ];
-      }
+    $current_page = $this->getCurrentPage($form, $form_state);
+    if ($current_page && $this->getYamlFormSetting('wizard_progress_bar') || $this->getYamlFormSetting('wizard_progress_pages') || $this->getYamlFormSetting('wizard_progress_percentage')) {
+      $form['progress'] = [
+        '#theme' => 'yamlform_progress',
+        '#yamlform' => $this->getYamlForm(),
+        '#current_page' => $current_page,
+      ];
     }
 
     // Append elements to the form.
     $form['elements'] = $elements;
 
-    // Alter form via form handler.
-    $this->getYamlForm()->invokeHandlers('alterForm', $form, $form_state, $yamlform_submission);
-
-    // Add CSS and JS.
+    // Default: Add CSS and JS.
     // @see https://www.drupal.org/node/2274843#inline
     $form['#attached']['library'][] = 'yamlform/yamlform.form';
 
-    // Add custom CSS and JS.
+    // Assets: Add custom CSS and JS.
     // @see yamlform_css_alter()
     // @see yamlform_js_alter()
     $assets = [
@@ -300,15 +263,46 @@ class YamlFormSubmissionForm extends ContentEntityForm {
       $form['#attached']['library'][] = 'yamlform/yamlform.form.disable_back';
     }
 
-    // Attach details element save open/close library.
-    // This ensures that the library will be loaded even if the
-    // Form is used as a block or a node.
+    // Unsaved: Add unsaved message.
+    if ($this->getYamlFormSetting('form_unsaved')) {
+      $form['#attributes']['class'][] = 'js-yamlform-unsaved';
+      $form['#attached']['library'][] = 'yamlform/yamlform.form.unsaved';
+      $current_page = $this->getCurrentPage($form, $form_state);
+      if ($current_page && ($current_page != $this->getFirstPage($form, $form_state))) {
+        $form['#attributes']['data-yamlform-unsaved'] = TRUE;
+      }
+    }
+
+    // Novalidate: Add novalidate attribute to form if client side validation disabled.
+    if ($this->getYamlFormSetting('form_novalidate')) {
+      $form['#attributes']['novalidate'] = 'novalidate';
+    }
+
+    // Details toggle: Display collapse/expand all details link.
+    if ($this->getYamlFormSetting('form_details_toggle')) {
+      $form['#attributes']['class'][] = 'yamlform-details-toggle';
+      $form['#attached']['library'][] = 'yamlform/yamlform.element.details.toggle';
+    }
+
+    // Autofocus: Add autofocus class to form.
+    if ($this->entity->isNew() && $this->getYamlFormSetting('form_autofocus')) {
+      $form['#attributes']['class'][] = 'js-yamlform-autofocus';
+    }
+
+    // Details save: Attach details element save open/close library.
+    // This ensures that the library will be loaded even if the form is
+    // used as a block or a node.
     if ($this->config('yamlform.settings')->get('ui.details_save')) {
       $form['#attached']['library'][] = 'yamlform/yamlform.element.details.save';
     }
 
-    // Set current wizard or preview page.
-    $this->setFormCurrentPage($form, $form_state);
+    // Pages: Disable form auto submit on enter for wizard form pages only.
+    if ($this->getPages($form, $form_state)) {
+      $form['#attributes']['class'][] = 'js-yamlform-disable-autosubmit';
+    }
+
+    // Pages: Set current wizard or preview page.
+    $this->displayCurrentPage($form, $form_state);
 
     // Add #after_build callbacks.
     $form['#after_build'][] = '::afterBuild';
@@ -503,12 +497,12 @@ class YamlFormSubmissionForm extends ContentEntityForm {
   protected function actions(array $form, FormStateInterface $form_state) {
     /* @var $yamlform_submission \Drupal\yamlform\YamlFormSubmissionInterface */
     $yamlform_submission = $this->entity;
+    $yamlform = $this->getYamlForm();
 
     $element = parent::actions($form, $form_state);
 
     /* @var \Drupal\yamlform\YamlFormSubmissionInterface $yamlform_submission */
     $preview_mode = $this->getYamlFormSetting('preview');
-    $wizard = $form_state->get('wizard');
 
     // Remove the delete button from the form submission form.
     unset($element['delete']);
@@ -530,27 +524,29 @@ class YamlFormSubmissionForm extends ContentEntityForm {
     // Add confirm(ation) handler to submit button.
     $element['submit']['#submit'][] = '::confirmForm';
 
-    $wizard_enabled = ($wizard['total']) ? TRUE : FALSE;
+    $pages = $this->getPages($form, $form_state);
+    $current_page = $this->getCurrentPage($form, $form_state);
+    if ($pages) {
+      // Get current page element which can contain custom prev(ious) and next button
+      // labels.
+      $current_page_element = $this->getYamlForm()->getPage($current_page);
 
-    if ($wizard_enabled) {
-      $is_wizard_last_page = ($wizard['total'] == ($wizard['current'] + ($this->getYamlFormSetting('wizard_complete') ? 2 : 1))) ? TRUE : FALSE;
-      $is_preview_page = $this->isPreview($form_state);
-      $is_next_page_optional_preview = ($this->isNextPagePreview($form_state) && $preview_mode != DRUPAL_REQUIRED);
+      $is_first_page = ($current_page == $this->getFirstPage($form, $form_state)) ? TRUE : FALSE;
+      $is_last_page = (in_array($current_page, ['preview', 'complete', $this->getLastPage($form, $form_state)])) ? TRUE : FALSE;
+      $is_preview_page = ($current_page == 'preview');
+      $is_next_page_preview = ($this->getNextPage($form, $form_state) == 'preview') ? TRUE : FALSE;
+      $is_next_page_optional_preview = ($is_next_page_preview && $preview_mode != DRUPAL_REQUIRED);
 
       // Only show that save button if this is the last page of the wizard or
       // on preview page or right before the optional preview.
-      $element['submit']['#access'] = $is_wizard_last_page || $is_preview_page || $is_next_page_optional_preview;
+      $element['submit']['#access'] = $is_last_page || $is_preview_page || $is_next_page_optional_preview;
 
-      // Get current page which can contain custom prev(ious) and next button
-      // labels.
-      $current_page = $this->getYamlForm()->getPage($wizard['current']);
-
-      if ($wizard['current']) {
-        if ($this->isPreview($form_state)) {
+      if (!$is_first_page) {
+        if ($is_preview_page) {
           $previous_label = $this->getYamlFormSetting('preview_prev_button_label');
         }
         else {
-          $previous_label = (isset($current_page['#prev_button_label'])) ? $current_page['#prev_button_label'] : $this->getYamlFormSetting('wizard_prev_button_label');
+          $previous_label = (isset($current_page_element['#prev_button_label'])) ? $current_page_element['#prev_button_label'] : $this->getYamlFormSetting('wizard_prev_button_label');
         }
         $element['previous'] = [
           '#type' => 'submit',
@@ -561,13 +557,13 @@ class YamlFormSubmissionForm extends ContentEntityForm {
         ];
       }
 
-      if (!$is_wizard_last_page) {
-        if ($this->isNextPagePreview($form_state)) {
+      if (!$is_last_page) {
+        if ($is_next_page_preview) {
           $next_label = $this->getYamlFormSetting('preview_next_button_label');
           $next_class = 'yamlform-button--preview';
         }
         else {
-          $next_label = (isset($current_page['#next_button_label'])) ? $current_page['#next_button_label'] : $this->getYamlFormSetting('wizard_next_button_label');
+          $next_label = (isset($current_page_element['#next_button_label'])) ? $current_page_element['#next_button_label'] : $this->getYamlFormSetting('wizard_next_button_label');
           $next_class = 'yamlform-button--next';
         }
         $element['next'] = [
@@ -606,11 +602,7 @@ class YamlFormSubmissionForm extends ContentEntityForm {
     if ($form_state->getErrors()) {
       return;
     }
-    // Move wizard forward.
-    $wizard = $form_state->get('wizard');
-    $wizard['current']++;
-    $form_state->set('wizard', $wizard);
-
+    $form_state->set('current_page', $this->getNextPage($form, $form_state));
     $this->wizardSubmit($form, $form_state);
   }
 
@@ -623,11 +615,7 @@ class YamlFormSubmissionForm extends ContentEntityForm {
    *   The current state of the form.
    */
   public function previous(array &$form, FormStateInterface $form_state) {
-    // Move wizard back.
-    $wizard = $form_state->get('wizard');
-    $wizard['current']--;
-    $form_state->set('wizard', $wizard);
-
+    $form_state->set('current_page', $this->getPreviousPage($form, $form_state));
     $this->wizardSubmit($form, $form_state);
   }
 
@@ -804,9 +792,7 @@ class YamlFormSubmissionForm extends ContentEntityForm {
     $yamlform_submission = $this->getEntity();
 
     // Set current page.
-    $wizard = $form_state->get('wizard');
-    if ($wizard['total']) {
-      $current_page = $wizard['pages'][$wizard['current']];
+    if ($current_page = $this->getCurrentPage($form, $form_state)) {
       $yamlform_submission->setCurrentPage($current_page);
     }
 
@@ -859,38 +845,138 @@ class YamlFormSubmissionForm extends ContentEntityForm {
     }
   }
 
+  /****************************************************************************/
+  // Wizard page functions
+  /****************************************************************************/
+
   /**
-   * Initialize the form wizard state manager.
+   * Get visible wizard pages.
+   *
+   * Note: The array of pages is stored in the form's state so that it can be
+   * altered using hook_form_alter() and #validate callbacks.
    *
    * @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
+   *   Array of visible wizard pages.
    */
-  protected function initFormWizardState(array &$form, FormStateInterface $form_state) {
-    if ($form_state->get('wizard')) {
-      return;
+  protected function getPages(array &$form, FormStateInterface $form_state) {
+    if ($form_state->get('pages') === NULL) {
+      $pages = $this->getYamlForm()->getPages();
+      foreach ($pages as &$page) {
+        $page['#access'] = TRUE;
+      }
+      $form_state->set('pages', $pages);
     }
 
-    // Get pages, total, and current.
-    $pages = array_keys($this->getYamlForm()->getPages());
-    $total = count($pages);
-    $current = 0;
+    // Get pages and check #access.
+    $pages = $form_state->get('pages');
+    foreach ($pages as $page_key => $page) {
+      if ($page['#access'] === FALSE) {
+        unset($pages[$page_key]);
+      }
+    }
 
-    // Get current page from saved draft.
-    $current_page = $this->entity->getCurrentPage();
-    if ($current_page  && $this->draftEnabled()) {
-      $index = array_flip($pages);
-      if (isset($index[$current_page])) {
-        $current = $index[$current_page];
+    return $pages;
+  }
+
+  /**
+   * Get the current page's key.
+   *
+   * @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 string
+   *   The current page's key.
+   */
+  protected function getCurrentPage(array &$form, FormStateInterface $form_state) {
+    if ($form_state->get('current_page') === NULL) {
+      $pages = $this->getPages($form, $form_state);
+      if (empty($pages)) {
+        $form_state->set('current_page', '');
+      }
+      else {
+        $current_page = $this->entity->getCurrentPage();
+        if ($current_page && isset($pages[$current_page]) && $this->draftEnabled()) {
+          $form_state->set('current_page', $current_page);
+        }
+        else {
+          $form_state->set('current_page', YamlFormArrayHelper::getFirstKey($pages));
+        }
       }
     }
+    return $form_state->get('current_page');
+  }
+
+  /**
+   * Get first page's key.
+   *
+   * @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 null|string
+   *   The first page's key.
+   */
+  protected function getFirstPage(array &$form, FormStateInterface $form_state) {
+    $pages = $this->getPages($form, $form_state);
+    return YamlFormArrayHelper::getFirstKey($pages);
+  }
+
+  /**
+   * Get last page's key.
+   *
+   * @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 null|string
+   *   The last page's key.
+   */
+  protected function getLastPage(array &$form, FormStateInterface $form_state) {
+    $pages = $this->getPages($form, $form_state);
+    return YamlFormArrayHelper::getLastKey($pages);
+  }
+
+  /**
+   * Get next page's key.
+   *
+   * @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 null|string
+   *   The next page's key. NULL if there is no next page.
+   */
+  protected function getNextPage(array &$form, FormStateInterface $form_state) {
+    $pages = $this->getPages($form, $form_state);
+    $current_page = $this->getCurrentPage($form, $form_state);
+    return YamlFormArrayHelper::getNextKey($pages, $current_page);
+  }
 
-    $form_state->set('wizard', [
-      'total' => $total,
-      'current' => $current,
-      'pages' => $pages ,
-    ]);
+  /**
+   * Get previous page's key.
+   *
+   * @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 null|string
+   *   The previous page's key. NULL if there is no previous page.
+   */
+  protected function getPreviousPage(array &$form, FormStateInterface $form_state) {
+    $pages = $this->getPages($form, $form_state);
+    $current_page = $this->getCurrentPage($form, $form_state);
+    return YamlFormArrayHelper::getPreviousKey($pages, $current_page);
   }
 
   /**
@@ -901,8 +987,9 @@ class YamlFormSubmissionForm extends ContentEntityForm {
    * @param \Drupal\Core\Form\FormStateInterface $form_state
    *   The current state of the form.
    */
-  protected function setFormCurrentPage(array &$form, FormStateInterface $form_state) {
-    if ($this->isPreview($form_state)) {
+  protected function displayCurrentPage(array &$form, FormStateInterface $form_state) {
+    $current_page = $this->getCurrentPage($form, $form_state);
+    if ($current_page == 'preview') {
       // Hide elements.
       $form['elements']['#access'] = FALSE;
 
@@ -916,19 +1003,26 @@ class YamlFormSubmissionForm extends ContentEntityForm {
       ];
     }
     else {
-      $wizard = $form_state->get('wizard');
-      foreach ($wizard['pages'] as $index => $page_key) {
-        if ($index != $wizard['current']) {
-          $form['elements'][$page_key]['#access'] = FALSE;
-          $this->hideElements($form['elements'][$page_key]);
-        }
-        else {
-          $form['elements'][$page_key]['#type'] = 'container';
+      // Get all pages so that we can also hide skipped pages.
+      $pages = $this->getYamlForm()->getPages();
+      foreach ($pages as $page_key => $page) {
+        if (isset($form['elements'][$page_key])) {
+          if ($page_key != $current_page) {
+            $form['elements'][$page_key]['#access'] = FALSE;
+            $this->hideElements($form['elements'][$page_key]);
+          }
+          else {
+            $form['elements'][$page_key]['#type'] = 'container';
+          }
         }
       }
     }
   }
 
+  /****************************************************************************/
+  // Form state functions
+  /****************************************************************************/
+
   /**
    * Set form state to redirect to a trusted redirect response.
    *
@@ -1263,36 +1357,6 @@ class YamlFormSubmissionForm extends ContentEntityForm {
   }
 
   /**
-   * Determine if the form is in preview mode.
-   *
-   * @param \Drupal\Core\Form\FormStateInterface $form_state
-   *   The current state of the form.
-   *
-   * @return bool
-   *   TRUE if form is in preview.
-   */
-  protected function isPreview(FormStateInterface $form_state) {
-    $wizard = $form_state->get('wizard');
-    $page = $this->getYamlForm()->getPage($wizard['current']);
-    return ($page['#type'] == 'yamlform_preview') ? TRUE : FALSE;
-  }
-
-  /**
-   * Determine if the form's next page is preview mode.
-   *
-   * @param \Drupal\Core\Form\FormStateInterface $form_state
-   *   The current state of the form.
-   *
-   * @return bool
-   *   TRUE if the form's next page is preview.
-   */
-  protected function isNextPagePreview(FormStateInterface $form_state) {
-    $wizard = $form_state->get('wizard');
-    $page = $this->getYamlForm()->getPage($wizard['current'] + 1);
-    return ($page && $page['#type'] == 'yamlform_preview') ? TRUE : FALSE;
-  }
-
-  /**
    * Is the current form an entity reference from the source entity.
    *
    * @return bool
diff --git a/templates/yamlform-progress-bar.html.twig b/templates/yamlform-progress-bar.html.twig
index 75a2d72..3a4e0bf 100644
--- a/templates/yamlform-progress-bar.html.twig
+++ b/templates/yamlform-progress-bar.html.twig
@@ -5,8 +5,10 @@
  *
  * Available variables:
  * - yamlform: A form.
- * - pages: Array of wizard pages.
- * - current_page: Current wizard page.
+ * - pages: Associatve array of wizard pages.
+ * - progress: Array of wizard progress containing page titles.
+ * - current_page: Current wizard page key.
+ * - current_index: The current wizard page index.
  * - max_pages: Maximum number of pages to be displayed.
  *
  * @see template_preprocess_yamlform_progress_bar()
@@ -16,13 +18,13 @@
 #}
 {{ attach_library('yamlform/yamlform.progress.bar') }}
 
-{% if pages|length < max_pages %}
-<ol class="yamlform-progress-bar" data-steps="{{ pages|length }}">
-{% for index, title in pages %}{%
+{% if progress|length < max_pages %}
+<ol class="yamlform-progress-bar" data-steps="{{ progress|length }}">
+{% for index, title in progress %}{%
   set classes = [
     'yamlform-progress-bar__page',
-    index < current_page ? 'yamlform-progress-bar__page--done',
-    index == current_page ? 'yamlform-progress-bar__page--current',
+    index < current_index ? 'yamlform-progress-bar__page--done',
+    index == current_index ? 'yamlform-progress-bar__page--current',
   ]
 %}<li{{ attributes.setAttribute('class', '').addClass(classes) }}>
   <b>{{ title }}</b>{% if (loop.first	or loop.last) %}<span></span>{% endif %}
diff --git a/templates/yamlform-progress.html.twig b/templates/yamlform-progress.html.twig
index 8aed144..c49ad40 100644
--- a/templates/yamlform-progress.html.twig
+++ b/templates/yamlform-progress.html.twig
@@ -8,6 +8,7 @@
  * - pages: Array of wizard pages.
  * - current_page: Current wizard page.
  * - total_pages: Current wizard page.
+ * - summary: Summary of progress.
  * - percentage: Percentage completed.
  * - bar: A progress bar.
  *
@@ -22,10 +23,10 @@
 
   {{ bar }}
 
-  {% if pages or percentage %}
+  {% if summary or percentage %}
     <div class="yamlform-progress__status">
-      {% if pages %}
-        <span class="yamlform-progress__pages">{{ pages }}</span>
+      {% if summary %}
+        <span class="yamlform-progress__'summary">{{ summary }}</span>
         {% if percentage %}
           <span class="yamlform-progress__percentage">({{ percentage }})</span>
         {% endif %}
diff --git a/tests/modules/yamlform_test/config/install/yamlform.yamlform.test_form_wizard_custom.yml b/tests/modules/yamlform_test/config/install/yamlform.yamlform.test_form_wizard_custom.yml
new file mode 100644
index 0000000..6e55bed
--- /dev/null
+++ b/tests/modules/yamlform_test/config/install/yamlform.yamlform.test_form_wizard_custom.yml
@@ -0,0 +1,127 @@
+langcode: en
+status: true
+dependencies:
+  enforced:
+    module:
+      - yamlform_test
+uid: null
+template: false
+id: test_form_wizard_custom
+title: 'Test: Form: Wizard custom'
+description: 'Test a multiple step ''wizard'' form with custom hide/show pages logic.'
+elements: |
+  wizard_1:
+    '#type': wizard_page
+    '#title': 'Wizard page #1'
+    pages:
+      '#type': checkboxes
+      '#titke': 'Pages'
+      '#options':
+        wizard_1: Page 1
+        wizard_2: Page 2
+        wizard_3: Page 3
+        wizard_4: Page 4
+        wizard_5: Page 5
+        wizard_6: Page 6
+        wizard_7: Page 7
+      '#default_value':
+        - wizard_1
+        - wizard_2
+        - wizard_3
+        - wizard_4
+        - wizard_5
+        - wizard_6
+        - wizard_7
+  wizard_2:
+    '#type': wizard_page
+    '#title': 'Wizard page #2'
+  wizard_3:
+    '#type': wizard_page
+    '#title': 'Wizard page #3'
+  wizard_4:
+    '#type': wizard_page
+    '#title': 'Wizard page #4'
+  wizard_5:
+    '#type': wizard_page
+    '#title': 'Wizard page #5'
+  wizard_6:
+    '#type': wizard_page
+    '#title': 'Wizard page #6'
+  wizard_7:
+    '#type': wizard_page
+    '#title': 'Wizard page #7'
+css: ''
+javascript: ''
+settings:
+  page: true
+  page_submit_path: ''
+  page_confirm_path: ''
+  form_submit_label: Apply
+  form_exception_message: ''
+  form_closed_message: ''
+  form_confidential: false
+  form_confidential_message: ''
+  form_prepopulate: true
+  form_prepopulate_source_entity: false
+  form_novalidate: false
+  form_unsaved: false
+  form_disable_back: false
+  form_autofocus: false
+  form_details_toggle: false
+  wizard_progress_bar: true
+  wizard_progress_pages: true
+  wizard_progress_percentage: true
+  wizard_next_button_label: ''
+  wizard_prev_button_label: ''
+  wizard_start_label: ''
+  wizard_complete: true
+  wizard_complete_label: ''
+  preview: 0
+  preview_next_button_label: ''
+  preview_prev_button_label: ''
+  preview_message: ''
+  draft: false
+  draft_auto_save: false
+  draft_button_label: ''
+  draft_saved_message: ''
+  draft_loaded_message: ''
+  confirmation_type: page
+  confirmation_message: ''
+  confirmation_url: ''
+  limit_total: null
+  limit_total_message: ''
+  limit_user: null
+  limit_user_message: ''
+  entity_limit_total: null
+  entity_limit_user: null
+  results_disabled: true
+  results_disabled_ignore: true
+  token_update: false
+access:
+  create:
+    roles:
+      - anonymous
+      - authenticated
+    users: {  }
+  view_any:
+    roles: {  }
+    users: {  }
+  update_any:
+    roles: {  }
+    users: {  }
+  delete_any:
+    roles: {  }
+    users: {  }
+  purge_any:
+    roles: {  }
+    users: {  }
+  view_own:
+    roles: {  }
+    users: {  }
+  update_own:
+    roles: {  }
+    users: {  }
+  delete_own:
+    roles: {  }
+    users: {  }
+handlers: {  }
diff --git a/tests/modules/yamlform_test/yamlform_test.module b/tests/modules/yamlform_test/yamlform_test.module
index ab4e1f8..8287abb 100644
--- a/tests/modules/yamlform_test/yamlform_test.module
+++ b/tests/modules/yamlform_test/yamlform_test.module
@@ -19,6 +19,10 @@ function yamlform_test_theme() {
   ];
 }
 
+/******************************************************************************/
+// Alter options.
+/******************************************************************************/
+
 /**
  * Implements hook_yamlform_options_YAMLFORM_OPTIONS_ID_alter().
  */
@@ -28,7 +32,38 @@ function yamlform_test_yamlform_options_test_alter(array &$options, array &$elem
     'two' => t('Two'),
     'three' => t('Three'),
   ];
+}
+
+/******************************************************************************/
+// Alter forms.
+/******************************************************************************/
 
+/**
+ * Implements hook_yamlform_form_FORM_ID_alter().
+ */
+function yamlform_test_form_yamlform_submission_test_form_wizard_custom_form_alter(array &$form, FormStateInterface $form_state) {
+  $form['#validate'][] = '_yamlform_test_form_yamlform_submission_test_form_wizard_custom_form_validate';
+}
+
+/**
+ * Validate handler for 'test_form_wizard_custom'.
+ */
+function _yamlform_test_form_yamlform_submission_test_form_wizard_custom_form_validate(array &$form, FormStateInterface $form_state) {
+  if ($form_state->hasAnyErrors()) {
+    return;
+  }
+
+  // Ge submitted values.
+  $values  = $form_state->getValues();
+
+  // Alter pages based on selected 'pages'.
+  $pages = $form_state->get('pages');
+  foreach ($pages as $page_key => &$page) {
+    if (isset($form['elements'][$page_key])) {
+      $page['#access'] = in_array($page_key, $values['pages']);
+    }
+  }
+  $form_state->set('pages', $pages);
 }
 
 /**
@@ -48,6 +83,10 @@ function yamlform_test_form_yamlform_submission_test_form_validate_form_validate
   }
 }
 
+/******************************************************************************/
+// Generate elements.
+/******************************************************************************/
+
 /**
  * Implements hook_yamlform_load().
  */
