diff --git a/ajax_example/ajax_example.info.yml b/ajax_example/ajax_example.info.yml
new file mode 100644
index 0000000..866e240
--- /dev/null
+++ b/ajax_example/ajax_example.info.yml
@@ -0,0 +1,8 @@
+name: 'AJAX Example'
+type: module
+description: 'An example module showing how to use Drupal AJAX forms.'
+package: 'Example modules'
+core: 8.x
+dependencies:
+  - core:node
+  - drupal:examples
diff --git a/ajax_example/ajax_example.libraries.yml b/ajax_example/ajax_example.libraries.yml
new file mode 100644
index 0000000..f4daca9
--- /dev/null
+++ b/ajax_example/ajax_example.libraries.yml
@@ -0,0 +1,7 @@
+ajax_example.library:
+  version: 1.x
+  css:
+    theme:
+      css/ajax-example-theme.css: {}
+  js:
+    js/ajax-example.js: {}
diff --git a/ajax_example/ajax_example.links.menu.yml b/ajax_example/ajax_example.links.menu.yml
new file mode 100644
index 0000000..2f5aa27
--- /dev/null
+++ b/ajax_example/ajax_example.links.menu.yml
@@ -0,0 +1,76 @@
+ajax_example.description:
+  title: 'AJAX Example'
+  route_name: 'ajax_example.description'
+  expanded: TRUE
+
+ajax_example.simplest:
+  title: 'Simplest AJAX example'
+  route_name: 'ajax_example.simplest'
+  parent: ajax_example.description
+  weight: 0
+
+ajax_example.submit-driven:
+  title: 'Submit-driven AJAX'
+  route_name: 'ajax_example.submit_driven_ajax'
+  parent: ajax_example.description
+  weight: 1
+
+ajax_example.render-link:
+  title: 'AJAX link in a render array'
+  route_name: 'ajax_example.ajax_link_render'
+  parent: ajax_example.description
+  weight: 2
+
+ajax_example.wizard-example:
+  title: 'Wizard example'
+  route_name: 'ajax_example.wizard'
+  parent: ajax_example.description
+  weight: 2
+
+ajax_example.wizard-examplenojs:
+  title: 'Wizard example w/JS turned off'
+  route_name: 'ajax_example.wizardnojs'
+  parent: ajax_example.description
+  weight: 3
+
+ajax_example.autocomplete-user:
+  title: 'Autocomplete user with entity_autocomplete'
+  route_name: 'ajax_example.autocomplete_user'
+  parent: ajax_example.description
+  weight: 4
+
+ajax_example.autotextfields:
+  title: 'Generate textfields'
+  route_name: 'ajax_example.autotextfields'
+  parent: ajax_example.description
+  weight: 5
+
+ajax_example.dependent-dropdown:
+  title: 'Dependent dropdown'
+  route_name: 'ajax_example.dependent_dropdown'
+  parent: ajax_example.description
+  weight: 6
+
+ajax_example.dependent-dropdown-degrades:
+  title: 'Dependent dropdown degrades'
+  route_name: 'ajax_example.dependent_dropdown_degrades'
+  parent: ajax_example.description
+  weight: 7
+
+ajax_example.dependent-dropdown-degrades-nojava:
+  title: 'Dependent dropdown degrades w/JS turned off '
+  route_name: 'ajax_example.dependent_dropdown_degrades_nojava'
+  parent: ajax_example.description
+  weight: 8
+
+ajax_example.dynamic-dropdown:
+  title: 'Dynamic sections with graceful degradation'
+  route_name: 'ajax_example.dynamic_sections'
+  parent: ajax_example.description
+  weight: 9
+
+ajax_example.dynamic-dropdown-degrades-nojava-example:
+  title: 'Dynamic sections with graceful degradation, w/js turned off'
+  route_name: 'ajax_example.dynamic_form_sections'
+  parent: ajax_example.description
+  weight: 10
diff --git a/ajax_example/ajax_example.module b/ajax_example/ajax_example.module
new file mode 100644
index 0000000..1c2a6de
--- /dev/null
+++ b/ajax_example/ajax_example.module
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * @file
+ * AJAX Examples module file with basic examples.
+ */
+
+/**
+ * @defgroup ajax_example Example: AJAX
+ * @ingroup examples
+ * @{
+ * These examples show basic AJAX concepts.
+ *
+ * General documentation is available at
+ * @link ajax AJAX Framework documentation @endlink and at the
+ * @link http://drupal.org/node/752056 AJAX Forms handbook page @endlink.
+ *
+ * The several examples here demonstrate basic AJAX usage.
+ */
+
+/**
+ * @} End of "defgroup ajax_example".
+ */
diff --git a/ajax_example/ajax_example.routing.yml b/ajax_example/ajax_example.routing.yml
new file mode 100644
index 0000000..42977fc
--- /dev/null
+++ b/ajax_example/ajax_example.routing.yml
@@ -0,0 +1,120 @@
+ajax_example.description:
+  path: 'examples/ajax-example'
+  defaults:
+    _controller: '\Drupal\ajax_example\Controller\AjaxExampleController::description'
+    _title: 'AJAX Example'
+  requirements:
+    _permission: 'access content'
+
+ajax_example.simplest:
+  path: 'examples/ajax-example/simplest'
+  defaults:
+    _form: '\Drupal\ajax_example\Form\Simplest'
+    _title: 'Simplest AJAX example'
+  requirements:
+    _permission: 'access content'
+
+ajax_example.autotextfields:
+  path: 'examples/ajax-example/autotextfields'
+  defaults:
+    _form: '\Drupal\ajax_example\Form\Autotextfields'
+    _title: 'Generate textfields'
+  requirements:
+    _permission: 'access content'
+
+ajax_example.submit_driven_ajax:
+  path: 'examples/ajax-example/submit-driven-ajax'
+  defaults:
+    _form: '\Drupal\ajax_example\Form\SubmitDriven'
+    _title: 'Submit-driven AJAX'
+  requirements:
+    _permission: 'access content'
+
+ajax_example.dependent_dropdown:
+  path: 'examples/ajax-example/dependent-dropdown'
+  defaults:
+    _form: '\Drupal\ajax_example\Form\DependentDropdown'
+    _title: 'Dependent dropdown'
+  requirements:
+    _permission: 'access content'
+
+ajax_example.dependent_dropdown_degrades:
+  path: 'examples/ajax-example/dependent-dropdown-degrades'
+  defaults:
+    _form: '\Drupal\ajax_example\Form\DependentDropdownDegrades'
+    _title: 'Dependent dropdown degrades'
+  requirements:
+    _permission: 'access content'
+
+ajax_example.dependent_dropdown_degrades_nojava:
+  path: 'examples/ajax-example/dependent-dropdown-degrades-nojava/{no_js_use}'
+  defaults:
+    _form: '\Drupal\ajax_example\Form\DependentDropdownDegrades'
+    _title: 'Dependent dropdown degrades w/JS turned off'
+    no_js_use: TRUE
+  requirements:
+    _permission: 'access content'
+
+ajax_example.dynamic_sections:
+  path: 'examples/ajax-example/dynamic-sections'
+  defaults:
+    _form: '\Drupal\ajax_example\Form\DependentDropdownDegrades'
+    _title: 'Dynamic sections with graceful degradation'
+  requirements:
+    _permission: 'access content'
+
+ajax_example.dynamic_form_sections:
+  path: 'examples/ajax-example/dynamic-form-sections/{nojs}'
+  defaults:
+    _form: '\Drupal\ajax_example\Form\DynamicFormSections'
+    _title: 'Dynamic form sections w/JS turned off'
+    nojs: 'nojs'
+  requirements:
+    _permission: 'access content'
+
+ajax_example.wizard:
+  path: 'examples/ajax-example/wizard'
+  defaults:
+    _form: '\Drupal\ajax_example\Form\Wizard'
+    _title: 'Wizard with graceful degradation'
+  requirements:
+    _permission: 'access content'
+
+ajax_example.wizardnojs:
+  path: 'examples/ajax-example/wizard-nojs/{no_js_use}'
+  defaults:
+    _form: '\Drupal\ajax_example\Form\Wizard'
+    _title: 'Wizard with graceful degradation, w/JS turned off'
+    no_js_use: TRUE
+  requirements:
+    _permission: 'access content'
+
+ajax_example.ajax_link_render:
+  path: 'examples/ajax-example/ajax-link-renderable'
+  defaults:
+    _controller: '\Drupal\ajax_example\Controller\AjaxExampleController::renderLinkRenderableArray'
+    _title: 'AJAX link from a render array'
+  requirements:
+    _permission: 'access content'
+
+# This route is for an AJAX callback. It is used by the AJAX system on
+# ajax_example.ajax_link_render. It has a {nojs} parameter, which gives us
+# a way to know whether the request is an AJAX request or is from some other
+# source.
+ajax_example.ajax_link_callback:
+  path: 'examples/ajax-example/ajax-link-callback/{nojs}'
+  defaults:
+    _controller: '\Drupal\ajax_example\Controller\AjaxExampleController::ajaxLinkCallback'
+    # We provide a default value for {nojs}, so that it can be an optional
+    # parameter.
+    nojs: 'nojs'
+  requirements:
+    _permission: 'access content'
+
+ajax_example.autocomplete_user:
+  path: 'examples/ajax_example/user_autocomplete'
+  defaults:
+    _form: '\Drupal\ajax_example\Form\EntityAutocomplete'
+    _title: 'Autocomplete users with entity_autocomplete.'
+  requirements:
+    _permission: 'access content'
diff --git a/ajax_example/css/ajax-example-theme.css b/ajax_example/css/ajax-example-theme.css
new file mode 100644
index 0000000..357126a
--- /dev/null
+++ b/ajax_example/css/ajax-example-theme.css
@@ -0,0 +1,20 @@
+/*
+ * @file
+ * CSS for ajax_example.
+ *
+ * details on what this file does. It is not used in any other example.
+ */
+/* Hides the next button when not degrading to non-javascript browser */
+html.js .next-button {
+  display: none;
+}
+
+.button .ajax-hide-submit {
+  display: none;
+}
+
+/* Makes the next/choose button align to the right of the select control */
+.form-item-dropdown-first,
+.form-item-question-type-select {
+  display: inline-block;
+}
diff --git a/ajax_example/js/ajax-example.js b/ajax_example/js/ajax-example.js
new file mode 100644
index 0000000..b3ffc3b
--- /dev/null
+++ b/ajax_example/js/ajax-example.js
@@ -0,0 +1,26 @@
+/**
+ * @file
+ * JavaScript for ajax_example.
+ */
+
+(function ($) {
+
+  // Re-enable form elements that are disabled for non-ajax situations.
+  Drupal.behaviors.enableFormItemsForAjaxForms = {
+    attach: function () {
+    // If ajax is enabled.
+    if (Drupal.ajax) {
+      $('.enabled-for-ajax').removeAttr('disabled');
+    }
+
+    // Below is only for the demo case of showing with js turned off.
+    // It overrides the behavior of the CSS that would normally turn off
+    // the 'ok' button when JS is enabled. Here, for demonstration purposes,
+    // we have AJAX disabled but JS turned on, so use this to simulate.
+    if (!Drupal.ajax) {
+      $('.button .ajax-hide-submit').show();
+    }
+  }
+  };
+
+})(jQuery);
diff --git a/ajax_example/src/Controller/AjaxExampleController.php b/ajax_example/src/Controller/AjaxExampleController.php
new file mode 100644
index 0000000..3f596e0
--- /dev/null
+++ b/ajax_example/src/Controller/AjaxExampleController.php
@@ -0,0 +1,113 @@
+<?php
+
+namespace Drupal\ajax_example\Controller;
+
+use Drupal\Core\Ajax\AjaxResponse;
+use Drupal\Core\Ajax\AppendCommand;
+use Drupal\Core\Controller\ControllerBase;
+use Drupal\Core\Url;
+use Drupal\examples\Utility\DescriptionTemplateTrait;
+use Symfony\Component\HttpFoundation\Response;
+
+/**
+ * Controller routines for AJAX example routes.
+ */
+class AjaxExampleController extends ControllerBase {
+
+  use DescriptionTemplateTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getModuleName() {
+    return 'ajax_example';
+  }
+
+  /**
+   * Demonstrates a clickable AJAX-enabled link using the 'use-ajax' class.
+   *
+   * Because of the 'use-ajax' class applied here, the link submission is done
+   * without a page refresh.
+   *
+   * When using the AJAX framework outside the context of a form or a renderable
+   * array of type 'link', you have to include ajax.js explicitly.
+   *
+   * @return array
+   *   Form API array.
+   *
+   * @ingroup ajax_example
+   */
+  public function renderLinkRenderableArray() {
+    $build['my_div'] = [
+      '#markup' => $this->t('
+The link below has been rendered as an element with the #ajax property, so if
+javascript is enabled, ajax.js will try to submit it via an AJAX call instead
+of a normal page load. The URL also contains the "/nojs/" magic string, which
+is stripped if javascript is enabled, allowing the server code to tell by the
+URL whether JS was enabled or not, letting it do different things based on that.'),
+    ];
+    // We'll add a nice border element for our demo.
+    $build['ajax_link'] = [
+      '#type' => 'details',
+      '#title' => $this->t('This is the AJAX link'),
+      '#open' => TRUE,
+    ];
+    // We build the AJAX link.
+    $build['ajax_link']['link'] = [
+      '#type' => 'link',
+      '#title' => $this->t('Click me'),
+      // We have to ensure that Drupal's Ajax system is loaded.
+      '#attached' => ['library' => ['core/drupal.ajax']],
+      // We add the 'use-ajax' class so that Drupal's AJAX system can spring
+      // into action.
+      '#attributes' => ['class' => ['use-ajax']],
+      // The URL for this link element is the callback. In our case, it's route
+      // ajax_example.ajax_link_callback, which maps to ajaxLinkCallback()
+      // below. The route has a /{nojs} section, which is how the callback can
+      // know whether the request was made by AJAX or some other means where
+      // JavaScript won't be able to handle the result. If the {nojs} part of
+      // the path is replaced with 'ajax', then the request was made by AJAX.
+      '#url' => Url::fromRoute('ajax_example.ajax_link_callback', ['nojs' => 'ajax']),
+    ];
+    // We provide a DIV that AJAX can append some text into.
+    $build['ajax_link']['destination'] = [
+      '#type' => 'container',
+      '#attributes' => ['id' => ['ajax-example-destination-div']],
+    ];
+    return $build;
+  }
+
+  /**
+   * Callback for link example.
+   *
+   * Takes different logic paths based on whether Javascript was enabled.
+   * If $type == 'ajax', it tells this function that ajax.js has rewritten
+   * the URL and thus we are doing an AJAX and can return an array of commands.
+   *
+   * @param string $nojs
+   *   Either 'ajax' or 'nojs. Type is simply the normal URL argument to this
+   *   URL.
+   *
+   * @return string|array
+   *   If $type == 'ajax', returns an array of AJAX Commands.
+   *   Otherwise, just returns the content, which will end up being a page.
+   */
+  public function ajaxLinkCallback($nojs = 'ajax') {
+    error_log($nojs);
+    // Determine whether the request is coming from AJAX or not.
+    if ($nojs == 'ajax') {
+      $output = $this->t("This is some content delivered via AJAX");
+      $response = new AjaxResponse();
+      $response->addCommand(new AppendCommand('#ajax-example-destination-div', $output));
+
+      // See ajax_example_advanced.inc for more details on the available
+      // commands and how to use them.
+      // $page = array('#type' => 'ajax', '#commands' => $commands);
+      // ajax_deliver($response);
+      return $response;
+    }
+    $response = new Response($this->t("This is some content delivered via a page load."));
+    return $response;
+  }
+
+}
diff --git a/ajax_example/src/Form/Autotextfields.php b/ajax_example/src/Form/Autotextfields.php
new file mode 100644
index 0000000..207ade4
--- /dev/null
+++ b/ajax_example/src/Form/Autotextfields.php
@@ -0,0 +1,90 @@
+<?php
+
+namespace Drupal\ajax_example\Form;
+
+use Drupal\Core\Form\FormBase;
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Show/hide textfields based on AJAX-enabled checkbox clicks.
+ */
+class Autotextfields extends FormBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'ajax_example_autotextfields';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state) {
+    $form['ask_first_name'] = [
+      '#type' => 'checkbox',
+      '#title' => $this->t('Ask me my first name'),
+      '#ajax' => [
+        'callback' => '::prompt',
+        'wrapper' => 'textfields',
+        'effect' => 'fade',
+      ],
+    ];
+    $form['ask_last_name'] = [
+      '#type' => 'checkbox',
+      '#title' => $this->t('Ask me my last name'),
+      '#ajax' => [
+        'callback' => '::prompt',
+        'wrapper' => 'textfields',
+        'effect' => 'fade',
+      ],
+    ];
+
+    $form['textfields'] = [
+      '#title' => $this->t("Generated text fields for first and last name"),
+      '#prefix' => '<div id="textfields">',
+      '#suffix' => '</div>',
+      '#type' => 'fieldset',
+      '#description' => t('This is where we put automatically generated textfields'),
+    ];
+
+    // Since checkboxes return TRUE or FALSE, we have to check that
+    // $form_state has been filled as well as what it contains.
+    if (!empty($form_state->getValue('ask_first_name')) && $form_state->getValue('ask_first_name')) {
+      $form['textfields']['first_name'] = [
+        '#type' => 'textfield',
+        '#title' => $this->t('First Name'),
+      ];
+    }
+    if (!empty($form_state->getValue('ask_last_name')) && $form_state->getValue('ask_last_name')) {
+      $form['textfields']['last_name'] = [
+        '#type' => 'textfield',
+        '#title' => $this->t('Last Name'),
+      ];
+    }
+
+    $form['submit'] = [
+      '#type' => 'submit',
+      '#value' => $this->t('Click Me'),
+    ];
+
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+  }
+
+  /**
+   * Callback for autotextfields.
+   *
+   * Selects the piece of the form we want to use as replacement text and
+   * returns it as a form (renderable array).
+   */
+  public function prompt($form, FormStateInterface $form_state) {
+    return $form['textfields'];
+  }
+
+}
diff --git a/ajax_example/src/Form/DependentDropdown.php b/ajax_example/src/Form/DependentDropdown.php
new file mode 100644
index 0000000..964f145
--- /dev/null
+++ b/ajax_example/src/Form/DependentDropdown.php
@@ -0,0 +1,101 @@
+<?php
+
+namespace Drupal\ajax_example\Form;
+
+use Drupal\Core\Form\FormBase;
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Repopulate a dropdown based on form state.
+ */
+class DependentDropdown extends FormBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'ajax_example_dependentdropdown';
+  }
+
+  /**
+   * AJAX-based dropdown example form.
+   *
+   * A form with a dropdown whose options are dependent on a
+   * choice made in a previous dropdown.
+   *
+   * On changing the first dropdown, the options in the second
+   * are updated.
+   */
+  public function buildForm(array $form, FormStateInterface $form_state) {
+    $options_first = DependentDropdownDegrades::getFirstDropdownOptions();
+    // If we have a value for the first dropdown from $form_state['values'] we
+    // use this both as the default value for the first dropdown and also as a
+    // parameter to pass to the function that retrieves the options for the
+    // second dropdown.
+    $selected = !empty($form_state->getValue('dropdown_first')) ? $form_state->getValue('dropdown_first') : key($options_first);
+
+    $form['dropdown_first'] = [
+      '#type' => 'select',
+      '#title' => $this->t('Instrument Type'),
+      '#options' => $options_first,
+      '#default_value' => $selected,
+      // Bind an ajax callback to the change event (which is the default for the
+      // select form type) of the first dropdown. It will replace the second
+      // dropdown when rebuilt.
+      '#ajax' => [
+        // When 'event' occurs, Drupal will perform an ajax request in the
+        // background. Usually the default value is sufficient (eg. change for
+        // select elements), but valid values include any jQuery event,
+        // most notably 'mousedown', 'blur', and 'submit'.
+        // 'event' => 'change',.
+        'callback' => '::prompt',
+        'wrapper' => 'dropdown-second-replace',
+      ],
+    ];
+
+    $form['dropdown_second'] = [
+      '#type' => 'select',
+      '#title' => $options_first[$selected] . ' ' . $this->t('Instruments'),
+      // The entire enclosing div created here gets replaced when dropdown_first
+      // is changed.
+      '#prefix' => '<div id="dropdown-second-replace">',
+      '#suffix' => '</div>',
+      // When the form is rebuilt during ajax processing, the $selected variable
+      // will now have the new value and so the options will change.
+      '#options' => DependentDropdownDegrades::getSecondDropdownOptions($selected),
+      '#default_value' => !empty($form_state->getValue('dropdown_second')) ? $form_state->getValue('dropdown_second') : '',
+    ];
+    $form['submit'] = [
+      '#type' => 'submit',
+      '#value' => $this->t('Submit'),
+    ];
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    $trigger = (string) $form_state->getTriggeringElement()['#value'];
+    switch ($trigger) {
+      case 'Submit':
+        // Submit: We're done.
+        drupal_set_message($this->t('Your values have been submitted. dropdown_first=@first, dropdown_second=@second', [
+          '@first' => $form_state->getValue('dropdown_first'),
+          '@second' => $form_state->getValue('dropdown_second'),
+        ]));
+        return;
+    }
+    // 'Choose' or anything else will cause rebuild of the form and present
+    // it again.
+    $form_state->setRebuild();
+  }
+
+  /**
+   * Handles switching the available regions based on the selected theme.
+   */
+  public function prompt($form, FormStateInterface $form_state) {
+    return $form['dropdown_second'];
+  }
+
+}
diff --git a/ajax_example/src/Form/DependentDropdownDegrades.php b/ajax_example/src/Form/DependentDropdownDegrades.php
new file mode 100644
index 0000000..0fe8528
--- /dev/null
+++ b/ajax_example/src/Form/DependentDropdownDegrades.php
@@ -0,0 +1,226 @@
+<?php
+
+namespace Drupal\ajax_example\Form;
+
+use Drupal\Core\Form\FormBase;
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Dropdown form based on previous choices.
+ *
+ * A form with a dropdown whose options are dependent on a choice made in a
+ * previous dropdown.
+ *
+ * On changing the first dropdown, the options in the second
+ * are updated. Gracefully degrades if no javascript.
+ *
+ * A bit of CSS and javascript is required. The CSS hides the "add more" button
+ * if javascript is not enabled. The Javascript snippet is really only used
+ * to enable us to present the form in degraded mode without forcing the user
+ * to turn off Javascript.  Both of these are loaded by using the
+ * #attached FAPI property, so it is a good example of how to use that.
+ *
+ * The extra argument $no_js_use is here only to allow presentation of this
+ * form as if Javascript were not enabled. ajax_example_menu() provides two
+ * ways to call this form, one normal ($no_js_use = FALSE) and one simulating
+ * Javascript disabled ($no_js_use = TRUE).
+ */
+class DependentDropdownDegrades extends FormBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'ajax_example_dependentdropdowndegrades';
+  }
+
+  /**
+   * Helper function to populate the first dropdown.
+   *
+   * This would normally be pulling data from the database.
+   *
+   * @return array
+   *   Dropdown options.
+   */
+  public static function getFirstDropdownOptions() {
+    return [
+      'String' => 'String',
+      'Woodwind' => 'Woodwind',
+      'Brass' => 'Brass',
+      'Percussion' => 'Percussion',
+    ];
+  }
+
+  /**
+   * Helper function to populate the second dropdown.
+   *
+   * This would normally be pulling data from the database.
+   *
+   * @param string $key
+   *   This will determine which set of options is returned.
+   *
+   * @return array
+   *   Dropdown options
+   */
+  public static function getSecondDropdownOptions($key = '') {
+    switch ($key) {
+      case 'String':
+        $options = [
+          'Violin' => 'Violin',
+          'Viola' => 'Viola',
+          'Cello' => 'Cello',
+          'Double Bass' => 'Double Bass',
+        ];
+        break;
+
+      case 'Woodwind':
+        $options = [
+          'Flute' => 'Flute',
+          'Clarinet' => 'Clarinet',
+          'Oboe' => 'Oboe',
+          'Bassoon' => 'Bassoon',
+        ];
+        break;
+
+      case 'Brass':
+        $options = [
+          'Trumpet' => 'Trumpet',
+          'Trombone' => 'Trombone',
+          'French Horn' => 'French Horn',
+          'Euphonium' => 'Euphonium',
+        ];
+        break;
+
+      case 'Percussion':
+        $options = [
+          'Bass Drum' => 'Bass Drum',
+          'Timpani' => 'Timpani',
+          'Snare Drum' => 'Snare Drum',
+          'Tambourine' => 'Tambourine',
+        ];
+        break;
+
+      default:
+        $options = ['none' => 'none'];
+        break;
+    }
+    return $options;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state, $no_js_use = FALSE) {
+    $options_first = static::getFirstDropdownOptions();
+
+    // If we have a value for the first dropdown from $form_state['values'] we
+    // use this both as the default value for the first dropdown and also as a
+    // parameter to pass to the function that retrieves the options for the
+    // second dropdown.
+    $selected = !empty($form_state->getValue('dropdown_first')) ? $form_state->getValue('dropdown_first') : key($options_first);
+
+    // Attach the CSS and JS we need to show this with and without javascript.
+    // Without javascript we need an extra "Choose" button, and this is
+    // hidden when we have javascript enabled.
+    $form['#attached']['library'][] = 'ajax_example/ajax_example.library';
+
+    $form['dropdown_first_fieldset'] = [
+      '#type' => 'details',
+      '#open' => TRUE,
+    ];
+    $form['dropdown_first_fieldset']['dropdown_first'] = [
+      '#type' => 'select',
+      '#title' => $this->t('Instrument Type'),
+      '#options' => $options_first,
+      '#attributes' => ['class' => ['enabled-for-ajax']],
+
+    // The '#ajax' property allows us to bind a callback to the server whenever
+    // this form element changes. See ajax_example_autocheckboxes and
+    // ajax_example_dependent_dropdown in ajax_example.module for more details.
+      '#ajax' => [
+        'callback' => '::prompt',
+        'wrapper' => 'dropdown-second-replace',
+      ],
+    ];
+
+    // This simply allows us to demonstrate no-javascript use without
+    // actually turning off javascript in the browser. Removing the #ajax
+    // element turns off AJAX behaviors on that element and as a result
+    // ajax.js doesn't get loaded. This is for demonstration purposes only.
+    if ($no_js_use) {
+      unset($form['dropdown_first_fieldset']['dropdown_first']['#ajax']);
+    }
+
+    // Since we don't know if the user has js or not, we always need to output
+    // this element, then hide it with with css if javascript is enabled.
+    $form['dropdown_first_fieldset']['continue_to_second'] = [
+      '#type' => 'submit',
+      '#value' => $this->t('Choose'),
+      '#attributes' => ['class' => ['next-button']],
+    ];
+
+    $form['dropdown_second_fieldset'] = [
+      '#type' => 'details',
+      '#open' => TRUE,
+    ];
+    $form['dropdown_second_fieldset']['dropdown_second'] = [
+      '#type' => 'select',
+      '#title' => $options_first[$selected] . ' ' . $this->t('Instruments'),
+      '#prefix' => '<div id="dropdown-second-replace">',
+      '#suffix' => '</div>',
+      '#attributes' => ['class' => ['enabled-for-ajax']],
+    // When the form is rebuilt during processing (either AJAX or multistep),
+    // the $selected variable will now have the new value and so the options
+    // will change.
+      '#options' => static::getSecondDropdownOptions($selected),
+    ];
+    $form['dropdown_second_fieldset']['submit'] = [
+      '#type' => 'submit',
+      '#value' => $this->t('OK'),
+    // This class allows attached js file to override the disabled attribute,
+    // since it's not necessary in ajax-enabled form.
+      '#attributes' => ['class' => ['enabled-for-ajax']],
+    ];
+
+    // Disable dropdown_second if a selection has not been made on dropdown
+    // first.
+    if (empty($form_state->getValue('dropdown_first'))) {
+      $form['dropdown_second_fieldset']['dropdown_second']['#disabled'] = TRUE;
+      $form['dropdown_second_fieldset']['submit']['#disabled'] = FALSE;
+      $form['dropdown_second_fieldset']['dropdown_second']['#description'] = $this->t('You must make your choice on the first dropdown before changing this second one.');
+    }
+    return $form;
+  }
+
+  /**
+   * Selects just the second dropdown to be returned for re-rendering.
+   *
+   * @return array
+   *   Renderable array (the second dropdown).
+   */
+  public function prompt(array $form, FormStateInterface $form_state) {
+    return $form['dropdown_second_fieldset']['dropdown_second'];
+
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    // Switch ($form_state->getTriggeringElement()) {
+    // case t('OK'):
+    // Submit: We're done.
+    $trigger = $form_state->getTriggeringElement()['#value'];
+    switch ($trigger) {
+      case 'Choose':
+        $form_state->setRebuild();
+        break;
+
+      case 'OK':
+        drupal_set_message(t('Your values have been submitted. dropdown_first=@first, dropdown_second=@second', ['@first' => $form_state->getValue('dropdown_first'), '@second' => $form_state->getValue('dropdown_second')]));
+        break;
+    }
+    $form_state->setRebuild();
+  }
+
+}
diff --git a/ajax_example/src/Form/DynamicFormSections.php b/ajax_example/src/Form/DynamicFormSections.php
new file mode 100644
index 0000000..a95001a
--- /dev/null
+++ b/ajax_example/src/Form/DynamicFormSections.php
@@ -0,0 +1,231 @@
+<?php
+
+namespace Drupal\ajax_example\Form;
+
+use Drupal\Core\Form\FormBase;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Url;
+
+/**
+ * Dynamically-enabled form with graceful no-JS degradation.
+ *
+ * Example of a form with portions dynamically enabled or disabled, but with
+ * graceful degradation in the case of no JavaScript.
+ *
+ * The idea here is that certain parts of the form don't need to be displayed
+ * unless a given option is selected, but then they should be displayed and
+ * configured.
+ *
+ * The third $no_js_use argument is strictly for demonstrating operation
+ * without javascript, without making the user/developer turn off javascript.
+ */
+class DynamicFormSections extends FormBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'ajax_example_dynamicsectiondegrades';
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * @todo: URL logic doesn't work.
+   * @todo: library CSS and JS still don't load.
+   */
+  public function buildForm(array $form, FormStateInterface $form_state, $nojs = NULL) {
+    // Attach the CSS and JS we need to show this with and without javascript.
+    // Without javascript we need an extra "Choose" button, and this is
+    // hidden when we have javascript enabled.
+    $form = [];
+    $form['#attached']['library'][] = 'ajax_example/ajax_example.library';
+
+    // Explanatory text with helpful links.
+    $url = Url::fromRoute('ajax_example.dynamic_form_sections');
+    $url->setRouteParameter('nojs', 'ajax');
+    $form['description'] = [
+      '#markup' => $this->t('<p>This example demonstrates a form which dynamically
+      creates various sections based on the configuration in the form.</p>
+      <p>It deliberately allows graceful degradation to a non-javascript environment.
+      In a non-javascript environment, the "Choose" button next to the select control
+      is displayed; in a javascript environment it is hidden by the module CSS.</p>
+      <p>The basic idea here is that the form is built up based on
+      the selection in the question_type_select field, and it is built the same
+      whether we are in a javascript/AJAX environment or not.</p>
+      <p>The form is reachable through the URL path <code>@path</code>. This URL defaults to not
+      using AJAX features, in order to simulate a web browser where JavaScript
+      is disabled. You can add <code>/ajax</code> to the end of the url to show
+      how this form behaves when it uses AJAX to change the form elements.</p>
+      <ul>
+      <li>Try it with AJAX: <a href=":url_with_ajax">@path_with_ajax</a></li>
+      </ul>'//,
+/*        [
+          '@path' => Url::fromRoute('ajax_example.dynamic_form_sections')->getInternalPath(),
+          ':url_with_ajax' => $url,
+          '@path_with_ajax' => $url->getInternalPath(),
+        ]
+  */    )
+    ];
+    $form['question_type_select'] = [
+      // This is our select dropdown.
+      '#type' => 'select',
+      '#title' => t('Question style'),
+      // We have a variety of form items you can use to get input from the user.
+      '#options' => [
+        'Choose question style' => 'Choose question style',
+        'Multiple Choice' => 'Multiple Choice',
+        'True/False' => 'True/False',
+        'Fill-in-the-blanks' => 'Fill-in-the-blanks',
+      ],
+      // The #ajax section tells the AJAX system that whenever this dropdown
+      // emits an event, it should call the callback and put the resulting
+      // content into the wrapper we specify. The questions-fieldset-wrapper is
+      // defined below.
+      '#ajax' => [
+        'wrapper' => 'questions-fieldset-wrapper',
+        'callback' => '::promptCallback',
+      ],
+    ];
+    // The CSS for this module hides this next button if JS is enabled.
+    $form['question_type_submit'] = [
+      '#type' => 'submit',
+      '#value' => t('Choose'),
+      '#attributes' => ['class' => ['ajax-hide-submit']],
+      // No need to validate when submitting this.
+      '#limit_validation_errors' => [],
+      '#validate' => [],
+    ];
+
+    // This simply allows us to demonstrate no-javascript use without
+    // actually turning off javascript in the browser. Removing the #ajax
+    // element turns off AJAX behaviors on that element and as a result
+    // ajax.js doesn't get loaded.
+    if ($nojs == 'nojs') {
+      // Remove the #ajax from the above, so ajax.js won't be loaded.
+      unset($form['question_type_select']['#ajax']);
+    }
+
+    // This fieldset just serves as a container for the part of the form
+    // that gets rebuilt. It has a nice line around it so you can see it.
+    $form['questions_fieldset'] = [
+      '#type' => 'details',
+      '#title' => $this->t('Stuff will appear here'),
+      '#open' => TRUE,
+      // We set the ID of this fieldset to questions-fieldset-wrapper so the
+      // AJAX command can replace it.
+      '#attributes' => ['id' => 'questions-fieldset-wrapper'],
+    ];
+
+    // When the AJAX request comes in, or when the user hit 'Submit' if there is
+    // no JavaScript, the form state will tell us what the user has selected
+    // from the dropdown. We can look at the value of the dropdown to determine
+    // which secondary form to display.
+    if (!empty($form_state->getValue('question_type_select'))) {
+
+      $form['questions_fieldset']['question'] = [
+        '#markup' => t('Who was the first president of the U.S.?'),
+      ];
+      $question_type = $form_state->getValue('question_type_select');
+
+      // Build up a secondary form, based on the type of question the user
+      // chose.
+      switch ($question_type) {
+        case 'Multiple Choice':
+          $form['questions_fieldset']['question'] = [
+            '#type' => 'radios',
+            '#title' => t('Who was the first president of the United States'),
+            '#options' => [
+              'George Bush' => 'George Bush',
+              'Adam McGuire' => 'Adam McGuire',
+              'Abraham Lincoln' => 'Abraham Lincoln',
+              'George Washington' => 'George Washington',
+            ],
+
+          ];
+          break;
+
+        case 'True/False':
+          $form['questions_fieldset']['question'] = [
+            '#type' => 'radios',
+            '#title' => $this->t('Was George Washington the first president of the United States?'),
+            '#options' => [
+              'George Washington' => 'True',
+              0 => 'False',
+            ],
+            '#description' => $this->t('Click "True" if you think George Washington was the first president of the United States.'),
+          ];
+          break;
+
+        case 'Fill-in-the-blanks':
+          $form['questions_fieldset']['question'] = [
+            '#type' => 'textfield',
+            '#title' => $this->t('Who was the first president of the United States'),
+            '#description' => $this->t('Please type the correct answer to the question.'),
+          ];
+          break;
+      }
+
+      $form['questions_fieldset']['submit'] = [
+        '#type' => 'submit',
+        '#value' => $this->t('Submit your answer'),
+      ];
+    }
+    return $form;
+  }
+
+  /**
+   * Final submit handler.
+   *
+   * Reports what values were finally set.
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    // This is only executed when a button is pressed, not when the AJAXfield
+    // select is changed.
+    // Now handle the case of the next, previous, and submit buttons.
+    // Only submit will result in actual submission, all others rebuild.
+    if ($form_state->getValue('question_type_submit') == 'Choose') {
+      $form_state->setValue('question_type_select', $form_state->getUserInput()['question_type_select']);
+      $form_state->setRebuild();
+    }
+
+    if ($form_state->getValue('submit') == 'Submit your answer') {
+      $form_state->setRebuild(FALSE);
+      $answer = $form_state->getValue('question');
+      print_r($answers);
+      // Special handling for the checkbox.
+      if ($answer == 1 && $form['questions_fieldset']['question']['#type'] == 'checkbox') {
+        $answer = $form['questions_fieldset']['question']['#title'];
+      }
+      if ($answer == $this->t('George Washington')) {
+        drupal_set_message($this->t('You got the right answer: @answer', ['@answer' => $answer]));
+      }
+      else {
+        drupal_set_message($this->t('Sorry, your answer (@answer) is wrong', ['@answer' => $answer]));
+      }
+      return;
+    }
+    // Sets the form to be rebuilt after processing.
+    $form_state->setRebuild();
+  }
+
+  /**
+   * Callback for the select element.
+   *
+   * Since the questions_fieldset part of the form has already been built during
+   * the AJAX request, we can return only that part of the form to the AJAX
+   * request, and it will insert that part into questions-fieldset-wrapper.
+   *
+   * @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 promptCallback($form, $form_state) {
+    return $form['questions_fieldset'];
+  }
+
+}
diff --git a/ajax_example/src/Form/EntityAutocomplete.php b/ajax_example/src/Form/EntityAutocomplete.php
new file mode 100644
index 0000000..53b0162
--- /dev/null
+++ b/ajax_example/src/Form/EntityAutocomplete.php
@@ -0,0 +1,126 @@
+<?php
+
+namespace Drupal\ajax_example\Form;
+
+use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Form\FormInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * A simple autocomplete form which looks up usernames.
+ *
+ * @ingroup ajax_example
+ */
+class EntityAutocomplete implements FormInterface, ContainerInjectionInterface {
+
+  use StringTranslationTrait;
+
+  /**
+   * The entity type manager service.
+   *
+   * We need this for the submit handler.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * Container injection factory.
+   *
+   * @param \Symfony\Component\DependencyInjection\ContainerInterface $container
+   *   The service discovery container.
+   *
+   * @return self
+   *   The form object.
+   */
+  public static function create(ContainerInterface $container) {
+    $form = new static(
+      $container->get('entity_type.manager')
+    );
+    $form->setStringTranslation($container->get('string_translation'));
+    return $form;
+  }
+
+  /**
+   * Constructor.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The entity type manager service.
+   */
+  public function __construct(EntityTypeManagerInterface $entity_type_manager) {
+    $this->entityTypeManager = $entity_type_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'ajax_example_autocomplete_user';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state) {
+    $form['info'] = [
+      '#markup' => '<div>' . t("This example uses the entity_autocomplete form "
+        . "element to select users. You'll need a few users on your system for "
+        . "it to make sense.") . '</div>',
+    ];
+
+    // Here we use the delightful entity_autocomplete form element. It allows us
+    // to consistently select entities. See https://www.drupal.org/node/2418529.
+    $form['users'] = [
+      // A type of entity_autocomplete lets Drupal know it should autocomplete
+      // entities.
+      '#type' => 'entity_autocomplete',
+      // We can specify entity types to autocomplete.
+      '#target_type' => 'user',
+      // Specifying #tags as TRUE allows for multiple selections, separated by
+      // commas.
+      '#tags' => TRUE,
+      '#title' => t('Choose a user. Separate with commas.'),
+    ];
+
+    $form['actions'] = [
+      '#type' => 'actions',
+    ];
+    $form['actions']['submit'] = [
+      '#type' => 'submit',
+      '#value' => $this->t('Submit'),
+    ];
+
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * Here we validate and signal an error if there are no users selected.
+   */
+  public function validateForm(array &$form, FormStateInterface $form_state) {
+    $state_users = $form_state->getValue('users');
+    if (empty($state_users)) {
+      $form_state->setErrorByName('users', 'There were no users selected.');
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * On submit, show the user the names of the users they selected.
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    $state_users = $form_state->getValue('users');
+    $users = [];
+    foreach ($state_users as $state_user) {
+      $uid = $state_user['target_id'];
+      $users[] = $this->entityTypeManager->getStorage('user')->load($uid)->getUsername();
+    }
+    drupal_set_message('These are your users: ' . implode(' ', $users));
+  }
+
+}
diff --git a/ajax_example/src/Form/Simplest.php b/ajax_example/src/Form/Simplest.php
new file mode 100644
index 0000000..4449ca5
--- /dev/null
+++ b/ajax_example/src/Form/Simplest.php
@@ -0,0 +1,80 @@
+<?php
+
+namespace Drupal\ajax_example\Form;
+
+use Drupal\Core\Form\FormBase;
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * A relatively simple AJAX demonstration form.
+ */
+class Simplest extends FormBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'ajax_example_simplest';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state) {
+    $form['changethis'] = [
+      '#title' => $this->t("Choose something and explain why"),
+      '#type' => 'select',
+      '#options' => [
+        'one' => 'one',
+        'two' => 'two',
+        'three' => 'three',
+      ],
+      '#ajax' => [
+        // #ajax has two required keys: callback and wrapper.
+        // 'callback' is a function that will be called when this element
+        // changes.
+        'callback' => '::promptCallback',
+        // 'wrapper' is the HTML id of the page element that will be replaced.
+        'wrapper' => 'replace_textfield_div',
+        // There are also several optional keys - see AjaxExampleAutoCheckboxes
+        // below for details on 'method', 'effect' and 'speed' and
+        // AjaxExampleDependentDropDown for 'event'.
+      ],
+    ];
+
+    // The 'replace_textfield_div' div will be replace whenever 'changethis' is
+    // updated.
+    $form['replace_textfield'] = [
+      '#type' => 'textfield',
+      '#title' => $this->t("Why"),
+      // The prefix/suffix provide the div that we're replacing, named by
+      // #ajax['wrapper'] above.
+      '#prefix' => '<div id="replace_textfield_div">',
+      '#suffix' => '</div>',
+    ];
+
+    // An AJAX request calls the form builder function for every change.
+    // We can change how we build the form based on $form_state.
+    $value = $form_state->getValue('changethis');
+    if (!empty($value)) {
+      $form['replace_textfield']['#description'] = $this->t("Say why you chose '@value'", ['@value' => $value]);
+    }
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    // No-op. Our form doesn't need a submit handler, because the form is never
+    // submitted. We add the method here so we fulfill FormInterface.
+  }
+
+  /**
+   * Handles switching the available regions based on the selected theme.
+   */
+  public function promptCallback($form, FormStateInterface $form_state) {
+    return $form['replace_textfield'];
+  }
+
+}
diff --git a/ajax_example/src/Form/SubmitDriven.php b/ajax_example/src/Form/SubmitDriven.php
new file mode 100644
index 0000000..c9f042c
--- /dev/null
+++ b/ajax_example/src/Form/SubmitDriven.php
@@ -0,0 +1,67 @@
+<?php
+
+namespace Drupal\ajax_example\Form;
+
+use Drupal\Core\Form\FormBase;
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Submit a form without a page reload.
+ */
+class SubmitDriven extends FormBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'ajax_example_autotextfields';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state) {
+    $form['box'] = [
+      '#type' => 'markup',
+      '#prefix' => '<div id="box">',
+      '#suffix' => '</div>',
+      '#markup' => '<h1>Initial markup for box</h1>',
+    ];
+
+    $form['submit'] = [
+      '#type' => 'submit',
+      '#ajax' => [
+        'callback' => '::prompt',
+        'wrapper' => 'box',
+      ],
+      '#value' => $this->t('Submit'),
+    ];
+
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+  }
+
+  /**
+   * Callback for submit_driven example.
+   *
+   * Select the 'box' element, change the markup in it, and return it as a
+   * renderable array.
+   *
+   * @return array
+   *   Renderable array (the box element)
+   */
+  public function prompt(array &$form, FormStateInterface $form_state) {
+    // In most cases, it is recommended that you put this logic in form
+    // generation rather than the callback. Submit driven forms are an
+    // exception, because you may not want to return the form at all.
+    $element = $form['box'];
+    $element['#markup'] = "Clicked submit ({$form_state->getValue('op')}): " . date('c');
+    return $element;
+  }
+
+}
diff --git a/ajax_example/src/Form/Wizard.php b/ajax_example/src/Form/Wizard.php
new file mode 100644
index 0000000..5a41802
--- /dev/null
+++ b/ajax_example/src/Form/Wizard.php
@@ -0,0 +1,231 @@
+<?php
+
+namespace Drupal\ajax_example\Form;
+
+use Drupal\Core\Form\FormBase;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Url;
+use Drupal\Core\Link;
+use Drupal\Core\Ajax\AjaxResponse;
+use Drupal\Core\Ajax\HtmlCommand;
+
+/**
+ * AJAX example wizard.
+ */
+class Wizard extends FormBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'ajax_example_wizard';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state, $no_js_use = FALSE) {
+    $url = Url::fromUri('internal:/examples/ajax-example/wizard-nojs');
+    $link = Link::fromTextAndUrl($this->t('examples/ajax-example/wizard-nojs'), $url)
+      ->toString();
+
+    // Prepare link for multiple arguments.
+    $urltwo = Url::fromUri('internal:/examples/ajax-example/wizard');
+    $linktwo = Link::fromTextAndUrl($this->t('examples/ajax-example/wizard'), $urltwo)
+      ->toString();
+
+    // $form['#prefix'] = '<div id="wizard-form-wrapper">';
+    // $form['#suffix'] = '</div>';
+    // We want to deal with hierarchical form values.
+    $form['#tree'] = TRUE;
+    $form['description'] = [
+      '#markup' => t('This example is a step-by-step wizard. The @link does it without page reloads; the @link1 is the same code but simulates a non-javascript environment, showing it with page reloads.', [
+        '@link' => $linktwo,
+        '@link1' => $link,
+      ]),
+    ];
+
+    $form['step'] = [
+      '#type' => 'hidden',
+      '#value' => !empty($form_state->getValue('step')) ? $form_state->getValue('step') : 1,
+    ];
+    print_r($form_state->getValue('step'));
+
+    if ($form['step']['#value'] == 1) {
+      $form['step1'] = [
+        '#type' => 'fieldset',
+        '#title' => $this->t('Step 1: Personal details'),
+      ];
+      $form['step1']['name'] = [
+        '#type' => 'textfield',
+        '#title' => $this->t('Your name'),
+        '#default_value' => empty($form_state->getValue([
+          'step1',
+          'name',
+        ]) ? '' : $form_state->getValue(['step1', 'name'])),
+        '#required' => TRUE,
+      ];
+
+      $form['next'] = [
+        '#type' => 'submit',
+        '#value' => $this->t('Next step'),
+        '#ajax' => [
+          'wrapper' => 'ajax-example-wizard',
+          'callback' => '::prompt',
+        ],
+      ];
+    }
+
+    // This simply allows us to demonstrate no-javascript use without
+    // actually turning off javascript in the browser. Removing the #ajax
+    // element turns off AJAX behaviors on that element and as a result
+    // ajax.js doesn't get loaded.
+    // For demonstration only! You don't need this.
+    if ($no_js_use) {
+      // Remove the #ajax from the above, so ajax.js won't be loaded.
+      // For demonstration only.
+      unset($form['next']['#ajax']);
+      unset($form['prev']['#ajax']);
+    }
+
+    return $form;
+  }
+
+  /**
+   * Wizard callback function.
+   *
+   * @param array $form
+   *   Form API form.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   Form API form.
+   *
+   * @return array
+   *   Form array.
+   */
+  public function prompt(array $form, FormStateInterface $form_state) {
+    return $form;
+  }
+
+  /**
+   * Save away the current information.
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    if ($form_state->getTriggeringElement()['#value'] == $this->t('Submit your information')) {
+      $value_message = $this->t('Your information has been submitted:') . ' ';
+      foreach ($form_state->getValue('value') as $step => $values) {
+        $value_message .= "$step: ";
+        foreach ($values as $key => $value) {
+          $value_message .= "$key=$value, ";
+        }
+      }
+      drupal_set_message($value_message);
+      $form_state->setRebuild(FALSE);
+      // Redirect to #action, else return.
+      return;
+    }
+    else {
+      $step = $form_state->getValue('step');
+      // Increment or decrement the step as needed. Recover values if they
+      // exist.
+      if ($form_state->getTriggeringElement()['#value']->__toString() == $this->t('Next step')) {
+        $step++;
+      }
+      elseif ($form_state->getTriggeringElement()['#value']->__toString() == $this->t('Previous step')) {
+        $step--;
+      }
+
+      switch ($step) {
+        case 1:
+          $form['step1'] = [
+            '#type' => 'fieldset',
+            '#title' => $this->t('Step 1: Personal details'),
+          ];
+          $form['step1']['name'] = [
+            '#type' => 'textfield',
+            '#title' => $this->t('Your name'),
+            '#default_value' => empty($form_state->getValue([
+              'step1',
+              'name',
+            ]) ? '' : $form_state->getValue(['step1', 'name'])),
+            '#required' => TRUE,
+          ];
+          $form_state->setValue('step', 1);
+          break;
+
+        case 2:
+          unset($form['step1']);
+          unset($form['next']);
+          $form['step2'] = [
+            '#type' => 'fieldset',
+            '#title' => t('Step 2: Street address info'),
+          ];
+          $form['step2']['address'] = [
+            '#type' => 'textfield',
+            '#title' => $this->t('Your street address'),
+            '#default_value' => empty($form_state->getValue([
+              'step2',
+              'address',
+            ]) ? '' : $form_state->getValue(['step2', 'address'])),
+            '#required' => TRUE,
+          ];
+          $form_state->setValue('step', $step);
+          break;
+
+        case 3:
+
+          $form['step3'] = [
+            '#type' => 'fieldset',
+            '#title' => $this->t('Step 3: City info'),
+          ];
+          $form['step3']['city'] = [
+            '#type' => 'textfield',
+            '#title' => $this->t('Your city'),
+            '#default_value' => empty($form_state->getValue([
+              'step3',
+              'city',
+            ]) ? '' : $form_state->getValue(['step3', 'city'])),
+            '#required' => TRUE,
+          ];
+          $form_state->setValue('step', $step);
+          break;
+      }
+      if ($step == 3) {
+
+        $form['submit'] = [
+          '#type' => 'submit',
+          '#value' => $this->t("Submit your information"),
+        ];
+      }
+      if ($step > 1 && !isset($form['prev'])) {
+        $form['prev'] = [
+          '#type' => 'submit',
+          '#value' => t("Previous step"),
+          // Since all info will be discarded, don't validate on 'prev'.
+          '#limit_validation_errors' => [],
+          // #submit is required to use #limit_validation_errors.
+          '#submit' => ['ajax_example_wizard_submit'],
+          '#ajax' => [
+            'wrapper' => 'ajax-example-wizard',
+            'callback' => '::prompt',
+          ],
+        ];
+      }
+      if ($step < 3 && !isset($form['next'])) {
+        $form['next'] = [
+          '#type' => 'submit',
+          '#value' => $this->t('Next step'),
+          '#limit_validation_errors' => [],
+          '#ajax' => [
+            'wrapper' => 'ajax-example-wizard',
+            'callback' => '::prompt',
+          ],
+        ];
+      }
+      $response = new AjaxResponse();
+      $response->addCommand(new HtmlCommand('#ajax-example-wizard', $form));
+      return $response;
+    }
+
+  }
+
+}
diff --git a/ajax_example/templates/description.html.twig b/ajax_example/templates/description.html.twig
new file mode 100644
index 0000000..0878c23
--- /dev/null
+++ b/ajax_example/templates/description.html.twig
@@ -0,0 +1,31 @@
+{#
+
+Description text for the Ajax Example.
+
+#}
+
+{% set simple_ajax_example = path('ajax_example.simplest') %}
+{% set ajax_generate_textfields = path('ajax_example.autotextfields') %}
+{% set ajax_submit = path('ajax_example.submit_driven_ajax') %}
+{% set ajax_dependent_dropdown = path('ajax_example.dependent_dropdown') %}
+{% set ajax_dependent_dropdown_degrade = path('ajax_example.dependent_dropdown_degrades') %}
+{% set ajax_dependent_dropdown_nojs = path('ajax_example.dependent_dropdown_degrades_nojava') %}
+{% set ajax_dynamic_dropdown = path('ajax_example.dynamic_sections') %}
+{% set ajax_dynamic_dropdown_nojs = path('ajax_example.dynamic_form_sections') %}
+{% set ajax_wizard_example = path('ajax_example.wizard') %}
+{% set ajax_wizard_example_nojs = path('ajax_example.wizardnojs') %}
+
+{% trans %}
+
+<p>The AJAX example module provides many examples of AJAX including forms, links, and AJAX commands.</p>
+<p><a href={{ simple_ajax_example }}>Simplest AJAX Example</a></p>
+<p><a href={{ ajax_generate_textfields }}>Generate textfields</a></p>
+<p><a href={{ ajax_submit }}>Submit-driven AJAX</a></p>
+<p><a href={{ ajax_dependent_dropdown }}>Dependent dropdown</a></p>
+<p><a href={{ ajax_dependent_dropdown_degrade }}>Dependent dropdown degrade</a></p>
+<p><a href={{ ajax_dependent_dropdown_nojs }}>Dependent dropdown degrades w/JS turned off</a></p>
+<p><a href={{ ajax_dynamic_dropdown }}>Dynamic Sections (with graceful degradation)</a></p>
+<p><a href={{ ajax_dynamic_dropdown_nojs }}>Dynamic Sections (with graceful degradation) w/js turned off</a></p>
+<p><a href={{ ajax_wizard_example }}>AJAX Wizard Example</a></p>
+<p><a href={{ ajax_wizard_example_nojs }}>AJAX Wizard Example w/JS turned off</a></p>
+{% endtrans %}
diff --git a/ajax_example/tests/src/Functional/AjaxExampleMenuTest.php b/ajax_example/tests/src/Functional/AjaxExampleMenuTest.php
new file mode 100644
index 0000000..b6acc0b
--- /dev/null
+++ b/ajax_example/tests/src/Functional/AjaxExampleMenuTest.php
@@ -0,0 +1,83 @@
+<?php
+
+namespace Drupal\Tests\ajax_example\Functional;
+
+use Drupal\Core\Url;
+use Drupal\Tests\BrowserTestBase;
+
+/**
+ * Verify functionalities of ajax_example.
+ *
+ * @group ajax_example
+ * @group examples
+ *
+ * @ingroup ajax_example
+ */
+class AjaxExampleMenuTest extends BrowserTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['ajax_example'];
+
+  /**
+   * The installation profile to use with this test.
+   *
+   * @var string
+   */
+  protected $profile = 'minimal';
+
+  /**
+   * Tests links.
+   */
+  public function testAjaxExampleLinks() {
+    // Login a user that can access content.
+    $this->drupalLogin(
+      $this->createUser(['access content', 'access user profiles'])
+    );
+
+    $assertion = $this->assertSession();
+
+    // Routes with menu links, and their form buttons.
+    $routes_with_menu_links = [
+      'ajax_example.description' => [],
+      'ajax_example.simplest' => [],
+      'ajax_example.ajax_link_render' => [],
+      'ajax_example.autotextfields' => ['Click Me'],
+      'ajax_example.dependent_dropdown' => ['Submit'],
+      'ajax_example.dependent_dropdown_degrades' => ['Choose', 'OK'],
+      'ajax_example.dependent_dropdown_degrades_nojava' => ['Choose', 'OK'],
+      'ajax_example.dynamic_sections' => ['Choose', 'OK'],
+      'ajax_example.dynamic_form_sections' => ['Choose'],
+      'ajax_example.wizard' => ['Next step'],
+      'ajax_example.wizardnojs' => ['Next step'],
+      'ajax_example.autocomplete_user' => ['Submit'],
+    ];
+
+    // Ensure the links appear in the tools menu sidebar.
+    $this->drupalGet('');
+    foreach (array_keys($routes_with_menu_links) as $route) {
+      $assertion->linkByHrefExists(Url::fromRoute($route)->getInternalPath());
+    }
+
+    // All our routes with their form buttons.
+    $routes = [
+      'ajax_example.submit_driven_ajax' => ['Submit'],
+      'ajax_example.ajax_link_render' => [],
+      'ajax_example.ajax_link_callback' => [],
+    ];
+
+    // Go to all the routes and click all the buttons.
+    $routes = array_merge($routes_with_menu_links, $routes);
+    foreach ($routes as $route => $buttons) {
+      $path = Url::fromRoute($route);
+      $this->drupalGet($path);
+      $assertion->statusCodeEquals(200);
+      foreach ($buttons as $button) {
+        $this->drupalPostForm($path, [], $button);
+        $assertion->statusCodeEquals(200);
+      }
+    }
+  }
+
+}
diff --git a/ajax_example/tests/src/FunctionalJavascript/EntityAutocompleteTest.php b/ajax_example/tests/src/FunctionalJavascript/EntityAutocompleteTest.php
new file mode 100644
index 0000000..e1588dc
--- /dev/null
+++ b/ajax_example/tests/src/FunctionalJavascript/EntityAutocompleteTest.php
@@ -0,0 +1,69 @@
+<?php
+
+namespace Drupal\Tests\ajax_example\FunctionalJavascript;
+
+use Drupal\Core\Url;
+use Drupal\FunctionalJavascriptTests\JavascriptTestBase;
+
+/**
+ * Tests the behavior of the entity_autocomplete example.
+ *
+ * @group ajax_example
+ */
+class EntityAutocompleteTest extends JavascriptTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['ajax_example'];
+
+  /**
+   * Test the behavior of the submit-driven AJAX example.
+   *
+   * Behaviors to test:
+   * - GET the route ajax_example.autocomplete_user.
+   * - Examine the DOM to make sure our change hasn't happened yet.
+   * - Send an event to the DOM to trigger the autocomplete.
+   * - Wait for the autocomplete request to complete.
+   * - Examine the DOM to see if our expected change happened.
+   * - Submit some names to see if our form processed the user properly.
+   */
+  public function testSubmitDriven() {
+    // Set up some accounts with known names.
+    $names = ['bb', 'bc'];
+    foreach ($names as $name) {
+      $this->createUser([], $name);
+    }
+
+    // Get our various Mink elements.
+    $assert = $this->assertSession();
+    $session = $this->getSession();
+    $page = $session->getPage();
+    // We'll be using the users field quite a bit, so let's make it a variable.
+    $users_field_name = 'edit-users';
+
+    // Get the form.
+    $this->drupalGet(Url::fromRoute('ajax_example.autocomplete_user'));
+    // Examine the DOM to make sure our change hasn't happened yet.
+    $assert->fieldValueEquals($users_field_name, '');
+
+    // Send an event to the DOM. This will start the autocomplete process.
+    $autocomplete_field = $page->findField($users_field_name);
+    $session->getDriver()->keyDown($autocomplete_field->getXpath(), 'b');
+
+    // Wait for the autocomplete request to complete.
+    $assert->waitOnAutocomplete();
+
+    // Examine the DOM to see if our expected change happened.
+    $results = $page->findAll('css', '.ui-autocomplete li');
+    $this->assertCount(2, $results);
+    foreach ($results as $result) {
+      $this->assertContains($result->getText(), $names);
+    }
+
+    // Submit to see if our form processed the user properly.
+    $this->submitForm([$users_field_name => 'bb, bc'], 'Submit');
+    $assert->pageTextContains('These are your users: bb bc');
+  }
+
+}
diff --git a/ajax_example/tests/src/FunctionalJavascript/SubmitDrivenTest.php b/ajax_example/tests/src/FunctionalJavascript/SubmitDrivenTest.php
new file mode 100644
index 0000000..e13d199
--- /dev/null
+++ b/ajax_example/tests/src/FunctionalJavascript/SubmitDrivenTest.php
@@ -0,0 +1,45 @@
+<?php
+
+namespace Drupal\Tests\ajax_example\FunctionalJavascript;
+
+use Drupal\Core\Url;
+use Drupal\FunctionalJavascriptTests\JavascriptTestBase;
+
+/**
+ * Tests the behavior of the submit-driven AJAX example.
+ *
+ * @group ajax_example
+ */
+class SubmitDrivenTest extends JavascriptTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['ajax_example'];
+
+  /**
+   * Test the behavior of the submit-driven AJAX example.
+   *
+   * Behaviors to test:
+   * - GET the route ajax_example.submit_driven_ajax.
+   * - Examine the DOM to make sure our change hasn't happened yet.
+   * - Submit the form.
+   * - Wait for the AJAX request to complete.
+   * - Examine the DOM to see if our expected change happened.
+   */
+  public function testSubmitDriven() {
+    // Get the session assertion object.
+    $assert = $this->assertSession();
+    // Get the page.
+    $this->drupalGet(Url::fromRoute('ajax_example.submit_driven_ajax'));
+    // Examine the DOM to make sure our change hasn't happened yet.
+    $assert->pageTextNotContains('Clicked submit (Submit):');
+    // Submit the form.
+    $this->submitForm([], 'Submit');
+    // Wait on the AJAX request.
+    $assert->assertWaitOnAjaxRequest();
+    // Compare DOM to our expectations.
+    $assert->pageTextContains('Clicked submit (Submit):');
+  }
+
+}
diff --git a/examples.module b/examples.module
index 7a6b6a9..1f7cd9a 100644
--- a/examples.module
+++ b/examples.module
@@ -35,6 +35,7 @@ function examples_toolbar() {
   // First, build an array of all example modules and their routes.
   // We resort to this hard-coded way so as not to muck up each example.
   $examples = [
+    'ajax_example' => 'ajax_example.description',
     'batch_example' => 'batch_example.form',
     'block_example' => 'block_example.description',
     'cache_example' => 'cache_example.description',
