diff --git a/core/modules/content_moderation/tests/src/FunctionalJavascript/WorkflowTypeEditFormTest.php b/core/modules/content_moderation/tests/src/FunctionalJavascript/WorkflowTypeEditFormTest.php
new file mode 100644
index 0000000000..cc00642817
--- /dev/null
+++ b/core/modules/content_moderation/tests/src/FunctionalJavascript/WorkflowTypeEditFormTest.php
@@ -0,0 +1,85 @@
+<?php
+
+namespace Drupal\Tests\content_moderation\FunctionalJavascript;
+
+use Drupal\FunctionalJavascriptTests\JavascriptTestBase;
+use \Drupal\block_content\Entity\BlockContentType;
+
+/**
+ * AJAX modal tests for the workflow type edit form.
+ *
+ * @group content_moderation
+ */
+class WorkflowTypeEditFormTest extends JavascriptTestBase {
+
+  /**
+   * Modules to install.
+   *
+   * @var array
+   */
+  public static $modules = [
+    'workflows',
+    'content_moderation',
+    'block_content',
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+    $user = $this->drupalCreateUser(['administer workflows']);
+    $this->drupalLogin($user);
+
+    // Create a custom block type.
+    $bundle = BlockContentType::create([
+      'id' => 'basic',
+      'label' => 'Basic block',
+      'revision' => FALSE,
+    ]);
+    $bundle->save();
+  }
+
+  /**
+   * Tests the workflow type edit form 'This workflow applies to:' section.
+   */
+  public function testWorkflowTypeEditForm() {
+    $this->drupalGet('admin/config/workflow/workflows/manage/editorial');
+
+    // Open the 'Custom block types' modal and select 'Basic block'.
+    $this->click('[data-drupal-selector="edit-type-settings-entity-types-container-entity-types-block-content-operations"] a');
+    $this->assertSession()->assertWaitOnAjaxRequest();
+    $modal = $this->assertSession()->waitForElementVisible('css', '#drupal-modal');
+    $this->assertTrue($modal->isVisible(), 'Modal window found.');
+    // Check the 'Basic block' checkbox.
+    $this->assertSession()->waitForElementVisible('css', '[data-drupal-selector="edit-bundles-basic"]')->check();
+    // Save the form and assert that the modal closes.
+    $save_button = $this->assertSession()->waitForElementVisible('css', '.ui-dialog button:contains(Save)');
+    $this->assertTrue($save_button->isVisible(), 'Save button found.');
+    $save_button->click();
+    $this->assertSession()->assertWaitOnAjaxRequest();
+    $this->assertFalse($modal->isVisible(), 'Modal window closed.');
+    // Assert that 'Basic block' shows in the UI as the selected Custom block
+    // types.
+    $selected_block_content = $this->assertSession()->waitForElementVisible('named', ['id', 'selected-block_content']);
+    $this->assertEqual('Basic block', $selected_block_content->getText());
+
+    // Open the 'Custom block types' modal again and deselect 'Basic block'.
+    $this->click('[data-drupal-selector="edit-type-settings-entity-types-container-entity-types-block-content-operations"] a');
+    $this->assertSession()->assertWaitOnAjaxRequest();
+    $modal = $this->assertSession()->waitForElementVisible('css', '#drupal-modal');
+    $this->assertTrue($modal->isVisible(), 'Modal window found.');
+    // Uncheck the 'Basic block' checkbox.
+    $this->assertSession()->waitForElementVisible('css', '[data-drupal-selector="edit-bundles-basic"]')->uncheck();
+     // Save the form and assert that the modal closes.
+    $save_button = $this->assertSession()->waitForElementVisible('css', '.ui-dialog button:contains(Save)');
+    $this->assertTrue($save_button->isVisible(), 'Save button found.');
+    $save_button->click();
+    $this->assertSession()->assertWaitOnAjaxRequest();
+    $this->assertFalse($modal->isVisible(), 'Modal window closed.');
+    // Assert that 'none' shows in the UI as the selected Custom block types.
+    $selected_block_content = $this->assertSession()->waitForElementVisible('named', ['id', 'selected-block_content']);
+    $this->assertEqual('none', $selected_block_content->getText());
+  }
+
+}
diff --git a/core/modules/workflows/src/Form/DialogFormTrait.php b/core/modules/workflows/src/Form/DialogFormTrait.php
new file mode 100644
index 0000000000..515e4ef7b0
--- /dev/null
+++ b/core/modules/workflows/src/Form/DialogFormTrait.php
@@ -0,0 +1,154 @@
+<?php
+
+namespace Drupal\workflows\Form;
+
+use Drupal\Core\Ajax\RedirectCommand;
+use Drupal\Core\Ajax\ReplaceCommand;
+use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
+use Drupal\Core\Ajax\AjaxResponse;
+use Drupal\Core\Ajax\CloseDialogCommand;
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Provides utilities for forms that want to be rendered in dialogs.
+ */
+trait DialogFormTrait {
+
+  /**
+   * Adds dialog support to a form.
+   *
+   * @param array $form
+   *   An associative array containing the structure of the form.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   * @param bool $create_cancel
+   *   If TRUE the create submit button will be created.q
+   * @param string $dialog_selector
+   *   The CSS selector for the associated dialog.
+   */
+  protected function buildFormDialog(array &$form, FormStateInterface $form_state, $create_cancel = FALSE, $dialog_selector = '#drupal-modal') {
+    if (!$this->isDialog()) {
+      return;
+    }
+    $form_state->set('dialog_selector', $dialog_selector);
+
+    $ajax_callback_added = FALSE;
+
+    if (!empty($form['actions']['submit'])) {
+      $form['actions']['submit']['#ajax'] = [
+        'callback' => '::submitFormDialog',
+        'event' => 'click',
+      ];
+      $ajax_callback_added = TRUE;
+    }
+
+    if ($create_cancel) {
+      $form['actions']['cancel'] = [
+        '#type' => 'submit',
+        '#value' => $this->t('Cancel'),
+        '#weight' => 100,
+      ];
+    }
+    if (!empty($form['actions']['cancel'])) {
+      // Replace 'Cancel' link button with a close dialog button.
+      $form['actions']['cancel'] = [
+        '#submit' => ['::noSubmit'],
+        '#limit_validation_errors' => [],
+        '#ajax' => [
+          'callback' => '::closeDialog',
+          'event' => 'click',
+        ],
+      ] + $form['actions']['cancel'];
+      $ajax_callback_added = TRUE;
+    }
+
+    if ($ajax_callback_added) {
+      $form['#attached']['library'][] = 'core/drupal.dialog.ajax';
+      $form['#attributes']['id'] = 'dialog-form';
+    }
+  }
+
+  /**
+   * Empty submit #ajax submit callback.
+   *
+   * This allows modal dialog to using ::submitCallback to validate and submit
+   * the form via one ajax required.
+   *
+   * @param array $form
+   *   An associative array containing the structure of the form.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   */
+  public function noSubmit(array &$form, FormStateInterface $form_state) {
+  }
+
+  /**
+   * Determines if the current request is for an AJAX dialog.
+   *
+   * @return bool
+   *   TRUE is the current request if for an AJAX dialog.
+   */
+  protected function isDialog() {
+    return in_array($this->getRequestWrapperFormat(), [
+      'drupal_ajax',
+      'drupal_dialog',
+      'drupal_modal',
+      'drupal_dialog.off_canvas',
+    ]);
+  }
+
+  /**
+   * Submit form dialog #ajax callback.
+   *
+   * @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 \Drupal\Core\Ajax\AjaxResponse
+   *   An AJAX response that display validation error messages or redirects
+   *   to a URL
+   */
+  public function submitFormDialog(array &$form, FormStateInterface $form_state) {
+    $response = new AjaxResponse();
+    if ($form_state->hasAnyErrors()) {
+      $form['status_messages'] = [
+        '#type' => 'status_messages',
+        '#weight' => -1000,
+      ];
+      $command = new ReplaceCommand('#dialog-form', $form);
+    }
+    else {
+      /** @var \Drupal\workflows\WorkflowInterface $workflow */
+      $workflow = $this->workflow ?: $this->getEntity();
+      $command = new RedirectCommand($workflow->toUrl('edit-form')->setAbsolute()->toString());
+    }
+    return $response->addCommand($command);
+  }
+
+  /**
+   * Close dialog #ajax callback.
+   *
+   * @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 bool|\Drupal\Core\Ajax\AjaxResponse
+   *   An AJAX response that display validation error messages.
+   */
+  public function closeDialog(array &$form, FormStateInterface $form_state) {
+    $selector = $form_state->get('dialog_selector');
+    return (new AjaxResponse())->addCommand(new CloseDialogCommand($selector));
+  }
+
+  /**
+   * @return mixed
+   */
+  protected function getRequestWrapperFormat() {
+    $wrapper_format = $this->getRequest()
+      ->get(MainContentViewSubscriber::WRAPPER_FORMAT);
+    return $wrapper_format;
+  }
+
+}
diff --git a/core/modules/workflows/src/Form/WorkflowEditForm.php b/core/modules/workflows/src/Form/WorkflowEditForm.php
index 764c0c9bc5..38b19298da 100644
--- a/core/modules/workflows/src/Form/WorkflowEditForm.php
+++ b/core/modules/workflows/src/Form/WorkflowEditForm.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\workflows\Form;
 
+use Drupal\Component\Serialization\Json;
 use Drupal\Core\Form\SubformState;
 use Drupal\workflows\WorkflowTypeFormInterface;
 use Drupal\workflows\Entity\Workflow;
@@ -80,22 +81,36 @@ public function form(array $form, FormStateInterface $form_state) {
       );
     }
 
+    $modal_attributes = [
+      'class' => ['use-ajax'],
+      'data-dialog-type' => 'modal',
+      'data-dialog-options' => Json::encode([
+        'width' => 700,
+      ]),
+    ];
+    $options = ['query' => ['destination' => $this->getEntity()->toUrl('edit-form')->toString()]];
     foreach ($states as $state) {
       $links = [
         'edit' => [
           'title' => $this->t('Edit'),
-          'url' => Url::fromRoute('entity.workflow.edit_state_form', ['workflow' => $workflow->id(), 'workflow_state' => $state->id()]),
-          'attributes' => ['aria-label' => $this->t('Edit @state state', ['@state' => $state->label()])],
+          'url' => Url::fromRoute('entity.workflow.edit_state_form',
+            ['workflow' => $workflow->id(), 'workflow_state' => $state->id()],
+            $options
+          ),
+          'attributes' => $modal_attributes + ['aria-label' => $this->t('Edit @state state', ['@state' => $state->label()])],
         ]
       ];
       if ($this->entity->access('delete-state:' . $state->id())) {
         $links['delete'] = [
           'title' => t('Delete'),
-          'url' => Url::fromRoute('entity.workflow.delete_state_form', [
-            'workflow' => $workflow->id(),
-            'workflow_state' => $state->id()
-          ]),
-          'attributes' => ['aria-label' => $this->t('Delete @state state', ['@state' => $state->label()])],
+          'url' => Url::fromRoute('entity.workflow.delete_state_form',
+            [
+              'workflow' => $workflow->id(),
+              'workflow_state' => $state->id(),
+            ],
+            $options
+          ),
+          'attributes' => $modal_attributes + ['aria-label' => $this->t('Delete @state state', ['@state' => $state->label()])],
         ];
       }
       $form['states_container']['states'][$state->id()] = [
@@ -116,7 +131,13 @@ public function form(array $form, FormStateInterface $form_state) {
       ];
     }
     $form['states_container']['state_add'] = [
-      '#markup' => $workflow->toLink($this->t('Add a new state'), 'add-state-form')->toString(),
+      '#type' => 'link',
+      '#title' => $this->t('Add a new state'),
+      '#url' => Url::fromRoute('entity.workflow.add_state_form',
+        ['workflow' => $workflow->id()],
+        $options
+      ),
+      '#attributes' => $modal_attributes,
     ];
 
     $header = [
@@ -147,13 +168,19 @@ public function form(array $form, FormStateInterface $form_state) {
     foreach ($workflow->getTransitions() as $transition) {
       $links['edit'] = [
         'title' => $this->t('Edit'),
-        'url' => Url::fromRoute('entity.workflow.edit_transition_form', ['workflow' => $workflow->id(), 'workflow_transition' => $transition->id()]),
-        'attributes' => ['aria-label' => $this->t('Edit \'@transition\' transition', ['@transition' => $transition->label()])],
+        'url' => Url::fromRoute('entity.workflow.edit_transition_form',
+          ['workflow' => $workflow->id(), 'workflow_transition' => $transition->id()],
+          $options
+        ),
+        'attributes' => $modal_attributes + ['aria-label' => $this->t('Edit @transition transition', ['@transition' => $transition->label()])],
       ];
       $links['delete'] = [
         'title' => t('Delete'),
-        'url' => Url::fromRoute('entity.workflow.delete_transition_form', ['workflow' => $workflow->id(), 'workflow_transition' => $transition->id()]),
-        'attributes' => ['aria-label' => $this->t('Delete \'@transition\' transition', ['@transition' => $transition->label()])],
+        'url' => Url::fromRoute('entity.workflow.delete_transition_form',
+          ['workflow' => $workflow->id(), 'workflow_transition' => $transition->id()],
+          $options
+        ),
+        'attributes' => $modal_attributes + ['aria-label' => $this->t('Delete @transition transition', ['@transition' => $transition->label()])],
       ];
       $form['transitions_container']['transitions'][$transition->id()] = [
         '#attributes' => ['class' => ['draggable']],
@@ -179,7 +206,13 @@ public function form(array $form, FormStateInterface $form_state) {
       ];
     }
     $form['transitions_container']['transition_add'] = [
-      '#markup' => $workflow->toLink($this->t('Add a new transition'), 'add-transition-form')->toString(),
+      '#type' => 'link',
+      '#title' => $this->t('Add a new transition'),
+      '#url' => Url::fromRoute('entity.workflow.add_transition_form',
+        ['workflow' => $workflow->id()],
+        $options
+      ),
+      '#attributes' => $modal_attributes,
     ];
 
     if ($workflow->getTypePlugin() instanceof WorkflowTypeFormInterface) {
@@ -189,6 +222,8 @@ public function form(array $form, FormStateInterface $form_state) {
       $subform_state = SubformState::createForSubform($form['type_settings'], $form, $form_state);
       $form['type_settings'] += $workflow->getTypePlugin()->buildConfigurationForm($form['type_settings'], $subform_state, $workflow);
     }
+    // Add the AJAX library to the form for dialog support.
+    $form['#attached']['library'][] = 'core/drupal.dialog.ajax';
 
     return $form;
   }
diff --git a/core/modules/workflows/src/Form/WorkflowEntityFormBase.php b/core/modules/workflows/src/Form/WorkflowEntityFormBase.php
new file mode 100644
index 0000000000..eca74fdb10
--- /dev/null
+++ b/core/modules/workflows/src/Form/WorkflowEntityFormBase.php
@@ -0,0 +1,33 @@
+<?php
+
+namespace Drupal\workflows\Form;
+
+use Drupal\Core\Entity\EntityForm;
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Class WorkflowEntityForm.
+ */
+class WorkflowEntityFormBase extends EntityForm {
+
+  use DialogFormTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state, $workflow_transition = NULL) {
+    $form = parent::buildForm($form, $form_state);
+    $this->buildFormDialog($form, $form_state, TRUE);
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function actions(array $form, FormStateInterface $form_state) {
+    $actions = parent::actions($form, $form_state);
+    unset($actions['delete']);
+    return $actions;
+  }
+
+}
diff --git a/core/modules/workflows/src/Form/WorkflowStateAddForm.php b/core/modules/workflows/src/Form/WorkflowStateAddForm.php
index 1827dd3e89..c1cc580088 100644
--- a/core/modules/workflows/src/Form/WorkflowStateAddForm.php
+++ b/core/modules/workflows/src/Form/WorkflowStateAddForm.php
@@ -2,14 +2,13 @@
 
 namespace Drupal\workflows\Form;
 
-use Drupal\Core\Entity\EntityForm;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Form\FormStateInterface;
 
 /**
  * Class WorkflowStateAddForm.
  */
-class WorkflowStateAddForm extends EntityForm {
+class WorkflowStateAddForm extends WorkflowEntityFormBase {
 
   /**
    * {@inheritdoc}
@@ -108,15 +107,11 @@ public function save(array $form, FormStateInterface $form_state) {
   }
 
   /**
-   * {@inheritdoc}
+   * Route title callback.
    */
-  protected function actions(array $form, FormStateInterface $form_state) {
-    $actions['submit'] = [
-      '#type' => 'submit',
-      '#value' => $this->t('Save'),
-      '#submit' => ['::submitForm', '::save'],
-    ];
-    return $actions;
+  public function getTitle($workflow = NULL) {
+    $title = $this->t('Add a new state to the @workflow', ['@workflow' => $workflow->label()]);
+    return $title;
   }
 
 }
diff --git a/core/modules/workflows/src/Form/WorkflowStateDeleteForm.php b/core/modules/workflows/src/Form/WorkflowStateDeleteForm.php
index c60045ca23..71d2785b63 100644
--- a/core/modules/workflows/src/Form/WorkflowStateDeleteForm.php
+++ b/core/modules/workflows/src/Form/WorkflowStateDeleteForm.php
@@ -12,6 +12,8 @@
  */
 class WorkflowStateDeleteForm extends ConfirmFormBase {
 
+  use DialogFormTrait;
+
   /**
    * The workflow entity the state being deleted belongs to.
    *
@@ -75,7 +77,11 @@ public function buildForm(array $form, FormStateInterface $form_state, WorkflowI
     }
     $this->workflow = $workflow;
     $this->stateId = $workflow_state;
-    return parent::buildForm($form, $form_state);
+
+    $form = parent::buildForm($form, $form_state);
+    $this->buildFormDialog($form, $form_state, TRUE);
+
+    return $form;
   }
 
   /**
diff --git a/core/modules/workflows/src/Form/WorkflowStateEditForm.php b/core/modules/workflows/src/Form/WorkflowStateEditForm.php
index f21508ff64..bd640f7538 100644
--- a/core/modules/workflows/src/Form/WorkflowStateEditForm.php
+++ b/core/modules/workflows/src/Form/WorkflowStateEditForm.php
@@ -2,15 +2,13 @@
 
 namespace Drupal\workflows\Form;
 
-use Drupal\Core\Entity\EntityForm;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Form\FormStateInterface;
-use Drupal\Core\Url;
 
 /**
  * Class WorkflowStateEditForm.
  */
-class WorkflowStateEditForm extends EntityForm {
+class WorkflowStateEditForm extends WorkflowEntityFormBase {
 
   /**
    * The ID of the state that is being edited.
@@ -67,45 +65,6 @@ public function form(array $form, FormStateInterface $form_state) {
       '#tree' => TRUE,
     ];
 
-    $header = [
-      'label' => $this->t('Transition'),
-      'state' => $this->t('To'),
-      'operations' => $this->t('Operations'),
-    ];
-    $form['transitions'] = [
-      '#type' => 'table',
-      '#header' => $header,
-      '#empty' => $this->t('There are no transitions to or from this state yet.'),
-    ];
-    foreach ($state->getTransitions() as $transition) {
-      $links['edit'] = [
-        'title' => $this->t('Edit'),
-        'url' => Url::fromRoute('entity.workflow.edit_transition_form', [
-          'workflow' => $workflow->id(),
-          'workflow_transition' => $transition->id()
-        ]),
-      ];
-      $links['delete'] = [
-        'title' => t('Delete'),
-        'url' => Url::fromRoute('entity.workflow.delete_transition_form', [
-          'workflow' => $workflow->id(),
-          'workflow_transition' => $transition->id()
-        ]),
-      ];
-      $form['transitions'][$transition->id()] = [
-        'label' => [
-          '#markup' => $transition->label(),
-        ],
-        'state' => [
-          '#markup' => $transition->to()->label(),
-        ],
-        'operations' => [
-          '#type' => 'operations',
-          '#links' => $links,
-        ],
-      ];
-    }
-
     return $form;
   }
 
@@ -137,7 +96,7 @@ protected function copyFormValuesToEntity(EntityInterface $entity, array $form,
    */
   public function save(array $form, FormStateInterface $form_state) {
     /** @var \Drupal\workflows\WorkflowInterface $workflow */
-    $workflow = $this->entity;
+    $workflow = $this->getEntity();
     $workflow->save();
     drupal_set_message($this->t('Saved %label state.', [
       '%label' => $workflow->getState($this->stateId)->label(),
@@ -146,29 +105,11 @@ public function save(array $form, FormStateInterface $form_state) {
   }
 
   /**
-   * {@inheritdoc}
+   * Route title callback.
    */
-  protected function actions(array $form, FormStateInterface $form_state) {
-    $actions['submit'] = [
-      '#type' => 'submit',
-      '#value' => $this->t('Save'),
-      '#submit' => ['::submitForm', '::save'],
-    ];
-
-    $actions['delete'] = [
-      '#type' => 'link',
-      '#title' => $this->t('Delete'),
-      '#access' => $this->entity->access('delete-state:' . $this->stateId),
-      '#attributes' => [
-        'class' => ['button', 'button--danger'],
-      ],
-      '#url' => Url::fromRoute('entity.workflow.delete_state_form', [
-        'workflow' => $this->entity->id(),
-        'workflow_state' => $this->stateId
-      ])
-    ];
-
-    return $actions;
+  public function getTitle($workflow = NULL, $workflow_state) {
+    $title = $this->t('Edit the @state state for the @workflow', ['@state' => $workflow->getState($workflow_state)->label(), '@workflow' => $workflow->label()]);
+    return $title;
   }
 
 }
diff --git a/core/modules/workflows/src/Form/WorkflowTransitionAddForm.php b/core/modules/workflows/src/Form/WorkflowTransitionAddForm.php
index fe3a40636d..f2173f2873 100644
--- a/core/modules/workflows/src/Form/WorkflowTransitionAddForm.php
+++ b/core/modules/workflows/src/Form/WorkflowTransitionAddForm.php
@@ -2,7 +2,6 @@
 
 namespace Drupal\workflows\Form;
 
-use Drupal\Core\Entity\EntityForm;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\workflows\State;
@@ -10,7 +9,7 @@
 /**
  * Class WorkflowTransitionAddForm.
  */
-class WorkflowTransitionAddForm extends EntityForm {
+class WorkflowTransitionAddForm extends WorkflowEntityFormBase {
 
   /**
    * {@inheritdoc}
@@ -102,13 +101,21 @@ protected function copyFormValuesToEntity(EntityInterface $entity, array $form,
       // Only do something once form validation is complete.
       return;
     }
-    /** @var \Drupal\workflows\WorkflowInterface $entity */
-    $values = $form_state->getValues();
-    $entity->addTransition($values['id'], $values['label'], array_filter($values['from']), $values['to']);
-    if (isset($values['type_settings'])) {
-      $configuration = $entity->getTypePlugin()->getConfiguration();
-      $configuration['transitions'][$values['id']] = $values['type_settings'][$entity->getTypePlugin()->getPluginId()];
-      $entity->set('type_settings', $configuration);
+    // Workflow::addTransition uses exceptions for validation, ignore them here
+    // so that the form validation takes place.
+    // @todo Remove need for try-catch https://www.drupal.org/node/2895310
+    try {
+      /** @var \Drupal\workflows\WorkflowInterface $entity */
+      $values = $form_state->getValues();
+      $entity->addTransition($values['id'], $values['label'], array_filter($values['from']), $values['to']);
+      if (isset($values['type_settings'])) {
+        $configuration = $entity->getTypePlugin()->getConfiguration();
+        $configuration['transitions'][$values['id']] = $values['type_settings'][$entity->getTypePlugin()->getPluginId()];
+        $entity->set('type_settings', $configuration);
+      }
+    }
+    catch(\InvalidArgumentException $e) {
+      // Do nothing, if there is an exception we want validation to handle it.
     }
   }
 
@@ -144,15 +151,11 @@ public function save(array $form, FormStateInterface $form_state) {
   }
 
   /**
-   * {@inheritdoc}
+   * Route title callback.
    */
-  protected function actions(array $form, FormStateInterface $form_state) {
-    $actions['submit'] = [
-      '#type' => 'submit',
-      '#value' => $this->t('Save'),
-      '#submit' => ['::submitForm', '::save'],
-    ];
-    return $actions;
+  public function getTitle($workflow = NULL) {
+    $title = $this->t('Add a new transition to the @workflow', ['@workflow' => $workflow->label()]);
+    return $title;
   }
 
 }
diff --git a/core/modules/workflows/src/Form/WorkflowTransitionDeleteForm.php b/core/modules/workflows/src/Form/WorkflowTransitionDeleteForm.php
index abcb41e664..7b3763cb5b 100644
--- a/core/modules/workflows/src/Form/WorkflowTransitionDeleteForm.php
+++ b/core/modules/workflows/src/Form/WorkflowTransitionDeleteForm.php
@@ -12,6 +12,8 @@
  */
 class WorkflowTransitionDeleteForm extends ConfirmFormBase {
 
+  use DialogFormTrait;
+
   /**
    * The workflow entity the transition being deleted belongs to.
    *
@@ -83,8 +85,12 @@ public function buildForm(array $form, FormStateInterface $form_state, WorkflowI
     catch (\InvalidArgumentException $e) {
       throw new NotFoundHttpException();
     }
+    $this->transitionId = $this->transition->id();
     $this->workflow = $workflow;
-    return parent::buildForm($form, $form_state);
+
+    $form = parent::buildForm($form, $form_state);
+    $this->buildFormDialog($form, $form_state, TRUE);
+    return $form;
   }
 
   /**
@@ -92,7 +98,7 @@ public function buildForm(array $form, FormStateInterface $form_state, WorkflowI
    */
   public function submitForm(array &$form, FormStateInterface $form_state) {
     $this->workflow
-      ->deleteTransition($this->transition->id())
+      ->deleteTransition($this->transitionId)
       ->save();
 
     drupal_set_message($this->t('%transition transition deleted.', ['%transition' => $this->transition->label()]));
diff --git a/core/modules/workflows/src/Form/WorkflowTransitionEditForm.php b/core/modules/workflows/src/Form/WorkflowTransitionEditForm.php
index 5bbabae6f7..ec59533b9f 100644
--- a/core/modules/workflows/src/Form/WorkflowTransitionEditForm.php
+++ b/core/modules/workflows/src/Form/WorkflowTransitionEditForm.php
@@ -2,16 +2,14 @@
 
 namespace Drupal\workflows\Form;
 
-use Drupal\Core\Entity\EntityForm;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Form\FormStateInterface;
-use Drupal\Core\Url;
 use Drupal\workflows\State;
 
 /**
  * Class WorkflowTransitionEditForm.
  */
-class WorkflowTransitionEditForm extends EntityForm {
+class WorkflowTransitionEditForm extends WorkflowEntityFormBase {
 
   /**
    * The ID of the transition that is being edited.
@@ -69,7 +67,7 @@ public function form(array $form, FormStateInterface $form_state) {
       '#options' => $states,
     ];
     $form['to'] = [
-      '#type' => 'radios',
+      '#type' => 'select',
       '#title' => $this->t('To'),
       '#required' => TRUE,
       '#default_value' => $transition->to()->id(),
@@ -123,15 +121,23 @@ protected function copyFormValuesToEntity(EntityInterface $entity, array $form,
       // Only do something once form validation is complete.
       return;
     }
-    /** @var \Drupal\workflows\WorkflowInterface $entity */
-    $values = $form_state->getValues();
-    $form_state->set('created_transition', FALSE);
-    $entity->setTransitionLabel($values['id'], $values['label']);
-    $entity->setTransitionFromStates($values['id'], array_filter($values['from']));
-    if (isset($values['type_settings'])) {
-      $configuration = $entity->getTypePlugin()->getConfiguration();
-      $configuration['transitions'][$values['id']] = $values['type_settings'][$entity->getTypePlugin()->getPluginId()];
-      $entity->set('type_settings', $configuration);
+    // Workflow::setTransitionFromStates uses exceptions for validation, ignore them here
+    // so that the form validation takes place.
+    // @todo Remove need for try-catch https://www.drupal.org/node/2895310
+    try {
+      /** @var \Drupal\workflows\WorkflowInterface $entity */
+      $values = $form_state->getValues();
+      $form_state->set('created_transition', FALSE);
+      $entity->setTransitionLabel($values['id'], $values['label']);
+      $entity->setTransitionFromStates($values['id'], array_filter($values['from']));
+      if (isset($values['type_settings'])) {
+        $configuration = $entity->getTypePlugin()->getConfiguration();
+        $configuration['transitions'][$values['id']] = $values['type_settings'][$entity->getTypePlugin()->getPluginId()];
+        $entity->set('type_settings', $configuration);
+      }
+    }
+    catch(\InvalidArgumentException $e) {
+      // Do nothing, if there is an exception we want validation to handle it.
     }
   }
 
@@ -140,7 +146,7 @@ protected function copyFormValuesToEntity(EntityInterface $entity, array $form,
    */
   public function save(array $form, FormStateInterface $form_state) {
     /** @var \Drupal\workflows\WorkflowInterface $workflow */
-    $workflow = $this->entity;
+    $workflow = $this->getEntity();
     $workflow->save();
     drupal_set_message($this->t('Saved %label transition.', [
       '%label' => $workflow->getTransition($this->transitionId)->label(),
@@ -149,30 +155,11 @@ public function save(array $form, FormStateInterface $form_state) {
   }
 
   /**
-   * {@inheritdoc}
+   * Route title callback.
    */
-  protected function actions(array $form, FormStateInterface $form_state) {
-    $actions['submit'] = [
-      '#type' => 'submit',
-      '#value' => $this->t('Save'),
-      '#submit' => ['::submitForm', '::save'],
-    ];
-
-    $actions['delete'] = [
-      '#type' => 'link',
-      '#title' => $this->t('Delete'),
-      // Deleting a transition is editing a workflow.
-      '#access' => $this->entity->access('edit'),
-      '#attributes' => [
-        'class' => ['button', 'button--danger'],
-      ],
-      '#url' => Url::fromRoute('entity.workflow.delete_transition_form', [
-        'workflow' => $this->entity->id(),
-        'workflow_transition' => $this->transitionId
-      ])
-    ];
-
-    return $actions;
+  public function getTitle($workflow = NULL, $workflow_transition) {
+    $title = $this->t('Edit the @transition transition for the @workflow', ['@transition' => $workflow->getTransition($workflow_transition)->label(), '@workflow' => $workflow->label()]);
+    return $title;
   }
 
 }
diff --git a/core/modules/workflows/tests/src/Functional/WorkflowUiTest.php b/core/modules/workflows/tests/src/Functional/WorkflowUiTest.php
index 7db8780194..1afe1dd7a9 100644
--- a/core/modules/workflows/tests/src/Functional/WorkflowUiTest.php
+++ b/core/modules/workflows/tests/src/Functional/WorkflowUiTest.php
@@ -174,7 +174,7 @@ public function testWorkflowCreation() {
     // Delete the transition.
     $workflow = $workflow_storage->loadUnchanged('test');
     $this->assertTrue($workflow->hasTransitionFromStateToState('published', 'published'), 'Can transition from published to published');
-    $this->clickLink('Delete');
+    $this->drupalGet('admin/config/workflow/workflows/manage/test/transition/save_and_publish/delete');
     $this->assertSession()->pageTextContains('Are you sure you want to delete Save and publish from Test?');
     $this->submitForm([], 'Delete');
     $workflow = $workflow_storage->loadUnchanged('test');
diff --git a/core/modules/workflows/tests/src/FunctionalJavascript/WorkflowEditFormTest.php b/core/modules/workflows/tests/src/FunctionalJavascript/WorkflowEditFormTest.php
new file mode 100644
index 0000000000..dbafff9d68
--- /dev/null
+++ b/core/modules/workflows/tests/src/FunctionalJavascript/WorkflowEditFormTest.php
@@ -0,0 +1,195 @@
+<?php
+
+namespace Drupal\Tests\workflows\FunctionalJavascript;
+
+use Drupal\FunctionalJavascriptTests\JavascriptTestBase;
+use Drupal\workflows\Entity\Workflow;
+
+
+/**
+ * AJAX modal tests for the workflow list page.
+ *
+ * @group content_moderation
+ */
+class WorkflowEditFormTest extends JavascriptTestBase {
+
+  /**
+   * Modules to install.
+   *
+   * @var array
+   */
+  public static $modules = [
+    'workflows',
+    'workflow_type_test',
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+    // Create a minimal workflow for testing.
+    $workflow = Workflow::create(['id' => 'test', 'type' => 'workflow_type_test']);
+    $workflow
+      ->addState('draft', 'Draft')
+      ->addState('published', 'Published')
+      ->addState('archived', 'Archived')
+      ->addTransition('publish', 'Publish', ['draft', 'published'], 'published')
+      ->addTransition('create_new_draft', 'Create New Draft', ['published'], 'draft')
+      ->addTransition('restore', 'Restore', ['archived'], 'published')
+      ->save();
+
+    $user = $this->drupalCreateUser(['administer workflows']);
+    $this->drupalLogin($user);
+  }
+
+  /**
+   * Test states operations uses a modal dialog.
+   */
+  public function testWorkflowStatesForm() {
+    $path = 'admin/config/workflow/workflows/manage/test';
+    $this->drupalGet($path);
+
+    // Assert that the state edit form is displayed in a modal.
+    $this->click('[data-drupal-selector="edit-states-draft-operations"] a:contains(Edit)');
+    $this->assertSession()->assertWaitOnAjaxRequest();
+    $modal = $this->assertSession()->waitForElementVisible('css', '#drupal-modal');
+    $this->assertTrue($modal->isVisible(), 'Modal window found.');
+    // Assert that the form errors are displayed in the modal.
+    $input = $this->assertSession()->elementExists('css', '#drupal-modal .form-item-label input[name="label"]');
+    $input->setValue("");
+    $save_button = $this->assertSession()->waitForElementVisible('css', '.ui-dialog-buttonpane .button--primary');
+    $this->assertTrue($save_button->isVisible(), 'Save button found.');
+    $save_button->click();
+    $this->assertSession()->assertWaitOnAjaxRequest();
+    $this->assertSession()->pageTextContains('Label field is required.');
+    // Assert that the cancel button closes the modal.
+    $this->click('.ui-dialog-buttonpane button:contains(Cancel)');
+    $this->assertSession()->assertWaitOnAjaxRequest();
+    $this->assertFalse($modal->isVisible(), 'Modal window closed.');
+    // Assert that saving the state saves the state and that we are stil at the
+    // same path.
+    $this->click('[data-drupal-selector="edit-states-draft-operations"] a:contains(Edit)');
+    $this->assertSession()->assertWaitOnAjaxRequest();
+    $input = $this->assertSession()->elementExists('css', '#drupal-modal .form-item-label input[name="label"]');
+    $input->setValue('Drafty');
+    $save_button = $this->assertSession()->waitForElementVisible('css', '.ui-dialog-buttonpane .button--primary');
+    $this->assertTrue($save_button->isVisible(), 'Save button found.');
+    $save_button->click();
+    $this->assertSession()->assertWaitOnAjaxRequest();
+    $this->assertSession()->addressEquals($path);
+    $this->assertSession()->pageTextContains('Saved Drafty state.');
+
+
+    // Assert that the state delete form is displayed in a modal.
+    $this->click('[data-drupal-selector="edit-states-draft"] .dropbutton-toggle button');
+    $delete_button = $this->assertSession()->waitForElementVisible('css', '[data-drupal-selector="edit-states-draft"] a:contains(Delete)');
+    $this->assertTrue($delete_button->isVisible(), 'Delete state button found.');
+    $delete_button->click();
+    $this->assertSession()->assertWaitOnAjaxRequest();
+    $modal = $this->assertSession()->waitForElementVisible('css', '#drupal-modal');
+    $this->assertTrue($modal->isVisible(), 'Modal window found.');
+    $delete_button = $this->assertSession()->waitForElementVisible('css', '.ui-dialog-buttonpane .button--primary');
+    $this->assertTrue($save_button->isVisible(), 'Delete button found.');
+    // Assert that the cancel button closes the modal.
+    $this->click('.ui-dialog-buttonpane button:contains(Cancel)');
+    $this->assertSession()->assertWaitOnAjaxRequest();
+    $this->assertFalse($modal->isVisible(), 'Modal window closed.');
+    // Assert that deleting the state deletes the state and that we are stil at
+    // the same path.
+    $this->drupalGet($path);
+    $this->click('[data-drupal-selector="edit-states-draft"] .dropbutton-toggle button');
+    $delete_button = $this->assertSession()->waitForElementVisible('css', '[data-drupal-selector="edit-states-draft"] a:contains(Delete)');
+    $this->assertTrue($delete_button->isVisible(), 'Delete state button found.');
+    $delete_button->click();
+    $this->assertSession()->assertWaitOnAjaxRequest();
+    $modal = $this->assertSession()->waitForElementVisible('css', '#drupal-modal');
+    $this->assertTrue($modal->isVisible(), 'Modal window found.');
+    $delete_button = $this->assertSession()->waitForElementVisible('css', '.ui-dialog-buttonpane .button--primary');
+    $this->assertTrue($save_button->isVisible(), 'Delete button found.');
+    $delete_button->click();
+    $this->assertSession()->assertWaitOnAjaxRequest();
+    $this->assertSession()->addressEquals($path);
+    $this->assertSession()->pageTextContains('State Drafty deleted.');
+ }
+
+  /**
+   * Test transitions operations uses a modal dialog.
+   */
+  public function testWorkflowTransitionsForm() {
+    $path = 'admin/config/workflow/workflows/manage/test';
+    $this->drupalGet($path);
+
+    // Assert that the transition edit form is displayed in a modal.
+    $this->click('[data-drupal-selector="edit-transitions-publish-operations"] a:contains(Edit)');
+    $this->assertSession()->assertWaitOnAjaxRequest();
+    $modal = $this->assertSession()->waitForElementVisible('css', '#drupal-modal');
+    $this->assertTrue($modal->isVisible(), 'Modal window found.');
+    // Assert that the form errors are displayed in the modal.
+    // Empty the Label text input which is required.
+    $label_input = $this->assertSession()->elementExists('css', '#drupal-modal .form-item-label input[name="label"]');
+    $label_input->setValue("");
+    // Check the 'Archived' state checkbox.
+    $archived_checkbox = $this->assertSession()->elementExists('css', '[data-drupal-selector="edit-from-archived"]');
+    $archived_checkbox->check();
+    // Try to save the form to trigger the errors.
+    $save_button = $this->assertSession()->waitForElementVisible('css', '.ui-dialog-buttonpane .button--primary');
+    $this->assertTrue($save_button->isVisible(), 'Save button found.');
+    $save_button->click();
+    $this->assertSession()->assertWaitOnAjaxRequest();
+    $this->assertSession()->pageTextContains('Label field is required.');
+    $this->assertSession()->pageTextContains('The transition from Archived to Published already exists.');
+    // Assert that the cancel button closes the modal with errors on the form.
+    $this->click('.ui-dialog-buttonpane button:contains(Cancel)');
+    $this->assertSession()->assertWaitOnAjaxRequest();
+    $this->assertFalse($modal->isVisible(), 'Modal window closed.');
+
+    // Assert that saving the transition saves the transition and that we are
+    // stil at the same path.
+    $this->click('[data-drupal-selector="edit-transitions-publish-operations"] a:contains(Edit)');
+    $this->assertSession()->assertWaitOnAjaxRequest();
+    $modal = $this->assertSession()->waitForElementVisible('css', '#drupal-modal');
+    $this->assertTrue($modal->isVisible(), 'Modal window found.');
+    // Assert that the form errors are displayed in the modal.
+    // Empty the Label text input which is required.
+    $label_input = $this->assertSession()->elementExists('css', '#drupal-modal .form-item-label input[name="label"]');
+    $label_input->setValue('Publishedlicius');
+    $save_button = $this->assertSession()->waitForElementVisible('css', '.ui-dialog-buttonpane .button--primary');
+    $this->assertTrue($save_button->isVisible(), 'Save button found.');
+    $save_button->click();
+    $this->assertSession()->assertWaitOnAjaxRequest();
+    $this->assertSession()->addressEquals($path);
+    $this->assertSession()->pageTextContains('Saved Publishedlicius transition.');
+
+
+
+    // Assert that the transition delete form is displayed in a modal.
+    $this->click('[data-drupal-selector="edit-transitions-create-new-draft"] .dropbutton-toggle button');
+    $delete_button = $this->assertSession()->waitForElementVisible('css', '[data-drupal-selector="edit-transitions-create-new-draft"] a:contains(Delete)');
+    $this->assertTrue($delete_button->isVisible(), 'Delete transition button found.');
+    $delete_button->click();
+    $this->assertSession()->assertWaitOnAjaxRequest();
+    $this->assertTrue($modal->isVisible(), 'Modal window found.');
+    $delete_button = $this->assertSession()->waitForElementVisible('css', '.ui-dialog-buttonpane .button--primary');
+    $this->assertTrue($save_button->isVisible(), 'Delete button found.');
+    // Assert that the cancel button closes the modal.
+    $this->click('.ui-dialog-buttonpane button:contains(Cancel)');
+    $this->assertSession()->assertWaitOnAjaxRequest();
+    $this->assertFalse($modal->isVisible(), 'Modal window closed.');
+
+    // Assert that deleting the transition deletes the transition and that we
+    // are stil at the same path.
+    $this->drupalGet($path);
+    $this->click('[data-drupal-selector="edit-transitions-create-new-draft"] .dropbutton-toggle button');
+    $delete_button = $this->assertSession()->waitForElementVisible('css', '[data-drupal-selector="edit-transitions-create-new-draft"] a:contains(Delete)');
+    $this->assertTrue($delete_button->isVisible(), 'Delete transition button found.');
+    $delete_button->click();
+    $delete_button = $this->assertSession()->waitForElementVisible('css', '.ui-dialog-buttonpane .button--primary');
+    $this->assertTrue($save_button->isVisible(), 'Delete button found.');
+    $delete_button->click();
+    $this->assertSession()->assertWaitOnAjaxRequest();
+    $this->assertSession()->addressEquals($path);
+    $this->assertSession()->pageTextContains('Create New Draft transition deleted.');
+  }
+
+}
diff --git a/core/modules/workflows/workflows.routing.yml b/core/modules/workflows/workflows.routing.yml
index 329ed10c48..7290c5f70b 100644
--- a/core/modules/workflows/workflows.routing.yml
+++ b/core/modules/workflows/workflows.routing.yml
@@ -2,7 +2,7 @@ entity.workflow.add_state_form:
   path: '/admin/config/workflow/workflows/manage/{workflow}/add_state'
   defaults:
     _entity_form: 'workflow.add-state'
-    _title: 'Add state'
+    _title_callback: '\Drupal\workflows\Form\WorkflowStateAddForm::getTitle'
   requirements:
     _entity_access: 'workflow.edit'
 
@@ -10,7 +10,7 @@ entity.workflow.edit_state_form:
   path: '/admin/config/workflow/workflows/manage/{workflow}/state/{workflow_state}'
   defaults:
     _entity_form: 'workflow.edit-state'
-    _title: 'Edit state'
+    _title_callback: '\Drupal\workflows\Form\WorkflowStateEditForm::getTitle'
   requirements:
     _entity_access: 'workflow.edit'
 
@@ -26,7 +26,7 @@ entity.workflow.add_transition_form:
   path: '/admin/config/workflow/workflows/manage/{workflow}/add_transition'
   defaults:
     _entity_form: 'workflow.add-transition'
-    _title: 'Add transition'
+    _title_callback: '\Drupal\workflows\Form\WorkflowTransitionAddForm::getTitle'
   requirements:
     _entity_access: 'workflow.edit'
 
@@ -34,7 +34,7 @@ entity.workflow.edit_transition_form:
   path: '/admin/config/workflow/workflows/manage/{workflow}/transition/{workflow_transition}'
   defaults:
     _entity_form: 'workflow.edit-transition'
-    _title: 'Edit transition'
+    _title_callback: '\Drupal\workflows\Form\WorkflowTransitionEditForm::getTitle'
   requirements:
     _entity_access: 'workflow.edit'
 
diff --git a/core/themes/seven/css/components/dropbutton.component.css b/core/themes/seven/css/components/dropbutton.component.css
index 2cf014107f..ef369ceeff 100644
--- a/core/themes/seven/css/components/dropbutton.component.css
+++ b/core/themes/seven/css/components/dropbutton.component.css
@@ -203,8 +203,22 @@
   -webkit-transition: none;
   transition: none;
 }
-.dropbutton-single .dropbutton-action a.use-ajax {
-  float: left;
+.dropbutton .dropbutton-action .ajax-progress {
+  position: absolute;
+  z-index: 2;
+  top: 0.2em;
+  right: 0.2em;
+  padding: 0 0 0 0.1em;
+}
+.dropbutton-multiple .dropbutton-action .ajax-progress {
+  right: 2.2em;
+  top: 0.15em;
+  margin-right: 0;
+}
+.dropbutton-multiple .secondary-action .ajax-progress {
+  top: auto;
+  bottom: 0.3em;
+  right: 2em;
 }
 
 /**
