diff --git a/js/webform.form.js b/js/webform.form.js
index 9af13058..7fbd91b3 100644
--- a/js/webform.form.js
+++ b/js/webform.form.js
@@ -145,4 +145,21 @@
     };
   }
 
+  /**
+   * Reacts to contextual links being added.
+   *
+   * @param {jQuery.Event} event
+   *   The `drupalContextualLinkAdded` event.
+   * @param {object} data
+   *   An object containing the data relevant to the event.
+   *
+   * @listens event:drupalContextualLinkAdded
+   */
+  $(document).on('drupalContextualLinkAdded', function (event, data) {
+    // Bind Ajax behaviors to all items showing the class.
+    // @todo Fix contextual links to work with use-ajax links in
+    //    https://www.drupal.org/node/2764931.
+    Drupal.attachBehaviors(data.$el[0]);
+  });
+
 })(jQuery, Drupal);
diff --git a/modules/webform_ui/src/Form/WebformUiElementEditForm.php b/modules/webform_ui/src/Form/WebformUiElementEditForm.php
index b0bc2d60..fb9cc673 100644
--- a/modules/webform_ui/src/Form/WebformUiElementEditForm.php
+++ b/modules/webform_ui/src/Form/WebformUiElementEditForm.php
@@ -5,6 +5,8 @@ namespace Drupal\webform_ui\Form;
 use Drupal\Component\Render\FormattableMarkup;
 use Drupal\Component\Utility\Xss;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Url;
+use Drupal\webform\Utility\WebformDialogHelper;
 use Drupal\webform\WebformInterface;
 use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
 
@@ -40,7 +42,25 @@ class WebformUiElementEditForm extends WebformUiElementFormBase {
     ]);
 
     $this->action = $this->t('updated');
-    return parent::buildForm($form, $form_state, $webform, $key);
+
+    $form = parent::buildForm($form, $form_state, $webform, $key);
+
+    if ($this->isModalDialog()) {
+      $form['actions']['delete'] = [
+        '#type' => 'link',
+        '#title' => $this->t('Delete'),
+        '#url' => new Url(
+          'entity.webform_ui.element.delete_form',
+          [
+            'webform' => $webform->id(),
+            'key' => $key,
+          ]
+        ),
+        '#attributes' => WebformDialogHelper::getModalDialogAttributes(700, ['button', 'button--danger']),
+      ];
+    }
+
+    return $form;
   }
 
 }
diff --git a/modules/webform_ui/src/Form/WebformUiElementFormBase.php b/modules/webform_ui/src/Form/WebformUiElementFormBase.php
index f63b054a..0f02b465 100644
--- a/modules/webform_ui/src/Form/WebformUiElementFormBase.php
+++ b/modules/webform_ui/src/Form/WebformUiElementFormBase.php
@@ -344,6 +344,9 @@ abstract class WebformUiElementFormBase extends FormBase implements WebformUiEle
    * {@inheritdoc}
    */
   protected function getRedirectUrl() {
+    if ($url = $this->getDestinationUrl()) {
+      return $url;
+    }
     return $this->webform->toUrl('edit-form', ['query' => ['element-update' => $this->key]]);
   }
 
diff --git a/modules/webform_ui/src/PathProcessor/WebformUiPathProcessor.php b/modules/webform_ui/src/PathProcessor/WebformUiPathProcessor.php
new file mode 100755
index 00000000..f1f5502f
--- /dev/null
+++ b/modules/webform_ui/src/PathProcessor/WebformUiPathProcessor.php
@@ -0,0 +1,37 @@
+<?php
+
+namespace Drupal\webform_ui\PathProcessor;
+
+use Drupal\Core\PathProcessor\OutboundPathProcessorInterface;
+use Drupal\Core\Render\BubbleableMetadata;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Path processor for webform UI.
+ */
+class WebformUiPathProcessor implements OutboundPathProcessorInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function processOutbound($path, &$options = [], Request $request = NULL, BubbleableMetadata $bubbleable_metadata = NULL) {
+    if (strpos($path, '/webform/') === FALSE  || !method_exists($request, 'getQueryString')) {
+      return $path;
+    }
+
+    if (strpos($request->getQueryString(), '_wrapper_format=') === FALSE) {
+      return $path;
+    }
+
+    $querystring = [];
+    parse_str($request->getQueryString(), $querystring);
+    if (!$querystring['destination']) {
+      return $path;
+    }
+
+    $destination = $querystring['destination'];
+    $options['query']['destination'] = $destination;
+    return $path;
+  }
+
+}
diff --git a/modules/webform_ui/src/WebformUiEntityForm.php b/modules/webform_ui/src/WebformUiEntityForm.php
index 118be445..b6657786 100644
--- a/modules/webform_ui/src/WebformUiEntityForm.php
+++ b/modules/webform_ui/src/WebformUiEntityForm.php
@@ -26,8 +26,6 @@ class WebformUiEntityForm extends WebformEntityForm {
       return $form;
     }
 
-    $element_dialog_attributes = WebformDialogHelper::getModalDialogAttributes(800);
-
     // Track which element has been updated.
     $element_update = FALSE;
     if ($this->getRequest()->query->has('element-update')) {
@@ -35,207 +33,15 @@ class WebformUiEntityForm extends WebformEntityForm {
       $form['#attached']['drupalSettings']['webformUiElementUpdate'] = $element_update;
     }
 
-    // Build table header.
-    $header = [];
-    $header['title'] = $this->t('Title');
-    $header['add'] = [
-      'data' => '',
-      'class' => [RESPONSIVE_PRIORITY_MEDIUM, 'webform-ui-element-operations'],
-    ];
-    $header['key'] = [
-      'data' => $this->t('Key'),
-      'class' => [RESPONSIVE_PRIORITY_LOW],
-    ];
-    $header['type'] = [
-      'data' => $this->t('Type'),
-      'class' => [RESPONSIVE_PRIORITY_LOW],
-    ];
-    if ($webform->hasFlexboxLayout()) {
-      $header['flex'] = [
-        'data' => $this->t('Flex'),
-        'class' => [RESPONSIVE_PRIORITY_LOW],
-      ];
-    }
-    $header['required'] = [
-      'data' => $this->t('Required'),
-      'class' => ['webform-ui-element-required', RESPONSIVE_PRIORITY_LOW],
-    ];
-    $header['weight'] = $this->t('Weight');
-    $header['parent'] = $this->t('Parent');
-    if (!$webform->isNew()) {
-      $header['operations'] = [
-        'data' => $this->t('Operations'),
-        'class' => ['webform-ui-element-operations'],
-      ];
-    }
+    $header = $this->getTableHeader();
+
 
     // Build table rows for elements.
     $rows = [];
     $elements = $this->getOrderableElements();
     $delta = count($elements);
     foreach ($elements as $element) {
-      $key = $element['#webform_key'];
-
-      $plugin_id = $this->elementManager->getElementPluginId($element);
-
-      /** @var \Drupal\webform\WebformElementInterface $webform_element */
-      $webform_element = $this->elementManager->createInstance($plugin_id);
-
-      $is_container = $webform_element->isContainer($element);
-      $is_root = $webform_element->isRoot();
-
-      // If disabled, display warning.
-      if ($webform_element->isDisabled()) {
-        $webform_element->displayDisabledWarning($element);
-      }
-
-      // Get row class names.
-      $row_class = ['draggable'];
-      if ($is_root) {
-        $row_class[] = 'tabledrag-root';
-        $row_class[] = 'webform-ui-element-root';
-      }
-      if (!$is_container) {
-        $row_class[] = 'tabledrag-leaf';
-      }
-      if ($is_container) {
-        $row_class[] = 'webform-ui-element-container';
-      }
-      if (!empty($element['#type'])) {
-        $row_class[] = 'webform-ui-element-type-' . $element['#type'];
-      }
-      $row_class[] = 'webform-ui-element-container';
-
-      // Add classes to updated element.
-      // @see Drupal.behaviors.webformUiElementsUpdate
-      if ($element_update && $element_update == $element['#webform_key']) {
-        $row_class[] = 'color-success';
-        $row_class[] = 'js-webform-ui-element-update';
-      }
-
-      $rows[$key]['#attributes']['class'] = $row_class;
-
-      $indentation = NULL;
-      if ($element['#webform_depth']) {
-        $indentation = [
-          '#theme' => 'indentation',
-          '#size' => $element['#webform_depth'],
-        ];
-      }
-
-      $rows[$key]['title'] = [
-        '#type' => 'link',
-        '#title' => $element['#admin_title'] ?: $element['#title'],
-        '#url' => new Url('entity.webform_ui.element.edit_form', ['webform' => $webform->id(), 'key' => $key]),
-        '#attributes' => $element_dialog_attributes,
-        '#prefix' => !empty($indentation) ? $this->renderer->render($indentation) : '',
-      ];
-
-      if ($is_container) {
-        $route_parameters = [
-          'webform' => $webform->id(),
-        ];
-        $route_options = ['query' => ['parent' => $key]];
-        $rows[$key]['add'] = [
-          '#type' => 'link',
-          '#title' => $this->t('Add element'),
-          '#url' => new Url('entity.webform_ui.element', $route_parameters, $route_options),
-          '#attributes' => WebformDialogHelper::getModalDialogAttributes(800, ['button', 'button-action', 'button--primary', 'button--small']),
-        ];
-      }
-      else {
-        $rows[$key]['add'] = ['#markup' => ''];
-      }
-
-      $rows[$key]['name'] = [
-        '#markup' => $element['#webform_key'],
-      ];
-
-      $rows[$key]['type'] = [
-        '#markup' => $webform_element->getPluginLabel(),
-      ];
-
-      if ($webform->hasFlexboxLayout()) {
-        $rows[$key]['flex'] = [
-          '#markup' => (empty($element['#flex'])) ? 1 : $element['#flex'],
-        ];
-      }
-
-      if ($webform_element->hasProperty('required')) {
-        $rows[$key]['required'] = [
-          '#type' => 'checkbox',
-          '#default_value' => (empty($element['#required'])) ? FALSE : TRUE,
-        ];
-      }
-      else {
-        $rows[$key]['required'] = ['#markup' => ''];
-      }
-
-      $rows[$key]['weight'] = [
-        '#type' => 'weight',
-        '#title' => $this->t('Weight for ID @id', ['@id' => $key]),
-        '#title_display' => 'invisible',
-        '#default_value' => $element['#weight'],
-        '#attributes' => [
-          'class' => ['row-weight'],
-        ],
-        '#delta' => $delta,
-      ];
-
-      $rows[$key]['parent']['key'] = [
-        '#parents' => ['webform_ui_elements', $key, 'key'],
-        '#type' => 'hidden',
-        '#value' => $key,
-        '#attributes' => [
-          'class' => ['row-key'],
-        ],
-      ];
-      $rows[$key]['parent']['parent_key'] = [
-        '#parents' => ['webform_ui_elements', $key, 'parent_key'],
-        '#type' => 'textfield',
-        '#size' => 20,
-        '#title' => $this->t('Parent'),
-        '#title_display' => 'invisible',
-        '#default_value' => $element['#webform_parent_key'],
-        '#attributes' => [
-          'class' => ['row-parent-key'],
-          'readonly' => 'readonly',
-        ],
-      ];
-
-      if (!$webform->isNew()) {
-        $rows[$key]['operations'] = [
-          '#type' => 'operations',
-        ];
-        $rows[$key]['operations']['#links']['edit'] = [
-          'title' => $this->t('Edit'),
-          'url' => new Url('entity.webform_ui.element.edit_form', ['webform' => $webform->id(), 'key' => $key]),
-          'attributes' => $element_dialog_attributes,
-        ];
-        // Issue #2741877 Nested modals don't work: when using CKEditor in a
-        // modal, then clicking the image button opens another modal,
-        // which closes the original modal.
-        // @todo Remove the below workaround once this issue is resolved.
-        if ($webform_element->getPluginId() == 'processed_text') {
-          unset($rows[$key]['operations']['#links']['edit']['attributes']);
-        }
-        $rows[$key]['operations']['#links']['duplicate'] = [
-          'title' => $this->t('Duplicate'),
-          'url' => new Url('entity.webform_ui.element.duplicate_form', [
-            'webform' => $webform->id(),
-            'key' => $key,
-          ]),
-          'attributes' => $element_dialog_attributes,
-        ];
-        $rows[$key]['operations']['#links']['delete'] = [
-          'title' => $this->t('Delete'),
-          'url' => new Url('entity.webform_ui.element.delete_form', [
-            'webform' => $webform->id(),
-            'key' => $key,
-          ]),
-          'attributes' => WebformDialogHelper::getModalDialogAttributes(700),
-        ];
-      }
+      $rows[$element['#webform_key']] = $this->getElementRow($element, $element_update, $delta);
     }
 
     // Must manually add local actions to the webform because we can't alter local
@@ -448,4 +254,255 @@ class WebformUiEntityForm extends WebformEntityForm {
     return $elements;
   }
 
+  /**
+   * Gets the elements table header.
+   *
+   * @return array
+   *   The header elements.
+   */
+  protected function getTableHeader() {
+    /** @var \Drupal\webform\WebformInterface $webform */
+    $webform = $this->getEntity();
+    $header = [];
+    $header['title'] = $this->t('Title');
+    $header['add'] = [
+      'data' => '',
+      'class' => [RESPONSIVE_PRIORITY_MEDIUM, 'webform-ui-element-operations'],
+    ];
+    if (!$this->isModalDialog()) {
+      $header['key'] = [
+        'data' => $this->t('Key'),
+        'class' => [RESPONSIVE_PRIORITY_LOW],
+      ];
+      $header['type'] = [
+        'data' => $this->t('Type'),
+        'class' => [RESPONSIVE_PRIORITY_LOW],
+      ];
+      if ($webform->hasFlexboxLayout()) {
+        $header['flex'] = [
+          'data' => $this->t('Flex'),
+          'class' => [RESPONSIVE_PRIORITY_LOW],
+        ];
+      }
+      $header['required'] = [
+        'data' => $this->t('Required'),
+        'class' => ['webform-ui-element-required', RESPONSIVE_PRIORITY_LOW],
+      ];
+    }
+    $header['weight'] = $this->t('Weight');
+    $header['parent'] = $this->t('Parent');
+    if (!$this->isModalDialog()) {
+      $header['operations'] = [
+        'data' => $this->t('Operations'),
+        'class' => ['webform-ui-element-operations'],
+      ];
+    }
+    return $header;
+  }
+
+  /**
+   * Gets an row for a single element.
+   *
+   * @param array $element
+   *   Webform element.
+   * @param bool|string $element_update
+   *   The name of the element being updated or FALSE if none.
+   * @param int $delta
+   *   The number of elements. @todo is this correct?
+   *
+   * @return array
+   *   The row for the element.
+   */
+  protected function getElementRow($element, $element_update, $delta) {
+    /** @var \Drupal\webform\WebformInterface $webform */
+    $webform = $this->getEntity();
+
+    $row = [];
+    $element_dialog_attributes = WebformDialogHelper::getModalDialogAttributes(800);
+    $key = $element['#webform_key'];
+
+    $plugin_id = $this->elementManager->getElementPluginId($element);
+
+    /** @var \Drupal\webform\WebformElementInterface $webform_element */
+    $webform_element = $this->elementManager->createInstance($plugin_id);
+
+    $is_container = $webform_element->isContainer($element);
+    $is_root = $webform_element->isRoot();
+
+    // If disabled, display warning.
+    if ($webform_element->isDisabled()) {
+      $webform_element->displayDisabledWarning($element);
+    }
+
+    // Get row class names.
+    $row_class = ['draggable'];
+    if ($is_root) {
+      $row_class[] = 'tabledrag-root';
+      $row_class[] = 'webform-ui-element-root';
+    }
+    if (!$is_container) {
+      $row_class[] = 'tabledrag-leaf';
+    }
+    if ($is_container) {
+      $row_class[] = 'webform-ui-element-container';
+    }
+    if (!empty($element['#type'])) {
+      $row_class[] = 'webform-ui-element-type-' . $element['#type'];
+    }
+    $row_class[] = 'webform-ui-element-container';
+
+    // Add classes to updated element.
+    // @see Drupal.behaviors.webformUiElementsUpdate
+    if ($element_update && $element_update == $element['#webform_key']) {
+      $row_class[] = 'color-success';
+      $row_class[] = 'js-webform-ui-element-update';
+    }
+
+    $row['#attributes']['class'] = $row_class;
+
+    $indentation = NULL;
+    if ($element['#webform_depth']) {
+      $indentation = [
+        '#theme' => 'indentation',
+        '#size' => $element['#webform_depth'],
+      ];
+    }
+
+    $row['title'] = [
+      '#type' => 'link',
+      '#title' => $element['#admin_title'] ?: $element['#title'],
+      '#url' => new Url('entity.webform_ui.element.edit_form', [
+          'webform' => $webform->id(),
+          'key' => $key
+        ]),
+      '#attributes' => $element_dialog_attributes,
+      '#prefix' => !empty($indentation) ? $this->renderer->render($indentation) : '',
+    ];
+
+    if ($is_container) {
+      $route_parameters = [
+        'webform' => $webform->id(),
+      ];
+      $route_options = ['query' => ['parent' => $key]];
+      $row['add'] = [
+        '#type' => 'link',
+        '#title' => $this->t('Add element'),
+        '#url' => new Url('entity.webform_ui.element', $route_parameters, $route_options),
+        '#attributes' => WebformDialogHelper::getModalDialogAttributes(800, [
+          'button',
+          'button-action',
+          'button--primary',
+          'button--small'
+        ]),
+      ];
+    }
+    else {
+      $row['add'] = ['#markup' => ''];
+    }
+    if (!$this->isModalDialog()) {
+      $row['name'] = [
+        '#markup' => $element['#webform_key'],
+      ];
+
+      $row['type'] = [
+        '#markup' => $webform_element->getPluginLabel(),
+      ];
+
+      if ($webform->hasFlexboxLayout()) {
+        $row['flex'] = [
+          '#markup' => (empty($element['#flex'])) ? 1 : $element['#flex'],
+        ];
+      }
+
+      if ($webform_element->hasProperty('required')) {
+        $row['required'] = [
+          '#type' => 'checkbox',
+          '#default_value' => (empty($element['#required'])) ? FALSE : TRUE,
+        ];
+      }
+      else {
+        $row['required'] = ['#markup' => ''];
+      }
+    }
+
+    $row['weight'] = [
+      '#type' => 'weight',
+      '#title' => $this->t('Weight for ID @id', ['@id' => $key]),
+      '#title_display' => 'invisible',
+      '#default_value' => $element['#weight'],
+      '#attributes' => [
+        'class' => ['row-weight'],
+      ],
+      '#delta' => $delta,
+    ];
+
+    $row['parent']['key'] = [
+      '#parents' => ['webform_ui_elements', $key, 'key'],
+      '#type' => 'hidden',
+      '#value' => $key,
+      '#attributes' => [
+        'class' => ['row-key'],
+      ],
+    ];
+    $row['parent']['parent_key'] = [
+      '#parents' => ['webform_ui_elements', $key, 'parent_key'],
+      '#type' => 'textfield',
+      '#size' => 20,
+      '#title' => $this->t('Parent'),
+      '#title_display' => 'invisible',
+      '#default_value' => $element['#webform_parent_key'],
+      '#attributes' => [
+        'class' => ['row-parent-key'],
+        'readonly' => 'readonly',
+      ],
+    ];
+
+    if (!$this->isModalDialog()) {
+      $row['operations'] = [
+        '#type' => 'operations',
+      ];
+      $row['operations']['#links']['edit'] = [
+        'title' => $this->t('Edit'),
+        'url' => new Url(
+          'entity.webform_ui.element.edit_form',
+          [
+            'webform' => $webform->id(),
+            'key' => $key,
+          ]
+        ),
+        'attributes' => $element_dialog_attributes,
+      ];
+      // Issue #2741877 Nested modals don't work: when using CKEditor in a
+      // modal, then clicking the image button opens another modal,
+      // which closes the original modal.
+      // @todo Remove the below workaround once this issue is resolved.
+      if ($webform_element->getPluginId() == 'processed_text') {
+        unset($row['operations']['#links']['edit']['attributes']);
+      }
+      $row['operations']['#links']['duplicate'] = [
+        'title' => $this->t('Duplicate'),
+        'url' => new Url(
+          'entity.webform_ui.element.duplicate_form',
+          [
+            'webform' => $webform->id(),
+            'key' => $key,
+          ]
+        ),
+        'attributes' => $element_dialog_attributes,
+      ];
+      $row['operations']['#links']['delete'] = [
+        'title' => $this->t('Delete'),
+        'url' => new Url(
+          'entity.webform_ui.element.delete_form',
+          [
+            'webform' => $webform->id(),
+            'key' => $key,
+          ]
+        ),
+        'attributes' => WebformDialogHelper::getModalDialogAttributes(700),
+      ];
+    }
+    return $row;
+  }
+
 }
diff --git a/modules/webform_ui/webform_ui.install b/modules/webform_ui/webform_ui.install
new file mode 100644
index 00000000..30187241
--- /dev/null
+++ b/modules/webform_ui/webform_ui.install
@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * @file
+ * Install, update and uninstall functions for the Webform UI module.
+ */
+
+use Drupal\Core\Cache\Cache;
+
+/**
+ * Implements hook_install().
+ */
+function webform_ui_install() {
+  // Copied from: outside_in_install().
+  // This module affects the rendering of blocks and of the page.
+  // @todo Remove in https://www.drupal.org/node/2783791.
+  Cache::invalidateTags(['rendered']);
+
+  // \Drupal\Core\Menu\ContextualLinkManager caches per-group definitions
+  // without associating the cache tag that would allow them to be cleared
+  // by its clearCachedDefinitions() implementation that is automatically
+  // invoked when modules are installed.
+  // @todo Remove when that is fixed in https://www.drupal.org/node/2773591.
+  \Drupal::service('cache.discovery')->deleteAll();
+}
+
+/**
+ * Implements hook_uninstall().
+ */
+function webform_ui_uninstall() {
+  webform_ui_install();
+}
diff --git a/modules/webform_ui/webform_ui.links.contextual.yml b/modules/webform_ui/webform_ui.links.contextual.yml
new file mode 100644
index 00000000..13ea3bf1
--- /dev/null
+++ b/modules/webform_ui/webform_ui.links.contextual.yml
@@ -0,0 +1,4 @@
+entity.webform.quickedit:
+  title: 'Quick Edit'
+  route_name: entity.webform.edit_form
+  group: webform
diff --git a/modules/webform_ui/webform_ui.module b/modules/webform_ui/webform_ui.module
index 8bc92106..e6971d43 100644
--- a/modules/webform_ui/webform_ui.module
+++ b/modules/webform_ui/webform_ui.module
@@ -5,6 +5,8 @@
  * Provides a simple user interface for building and maintaining webforms.
  */
 
+use Drupal\webform\Utility\WebformDialogHelper;
+
 /**
  * Implements hook_entity_type_alter().
  */
@@ -20,6 +22,7 @@ function webform_ui_entity_type_alter(array &$entity_types) {
     $handlers['form']['source'] = $handlers['form']['default'];
     $handlers['form']['default'] = 'Drupal\webform_ui\WebformUiEntityForm';
     $handlers['form']['duplicate'] = 'Drupal\webform_ui\WebformUiEntityForm';
+    $handlers['form']['quick_edit'] = 'Drupal\webform_ui\WebformUiEntityForm';
     $webform_entity_type->setHandlerClass('form', $handlers['form']);
   }
 
@@ -37,3 +40,26 @@ function webform_ui_entity_type_alter(array &$entity_types) {
   }
 
 }
+
+/**
+ * Implements hook_contextual_links_view_alter().
+ *
+ * Issue #2752637: hook_contextual_links_view_alter not getting called.
+ * @see https://www.drupal.org/node/2752637
+ *
+ * Issue #2773591: New contextual links are not available after a module is installed
+ * @see https://www.drupal.org/node/2773591
+ */
+function webform_ui_contextual_links_view_alter(&$element, $items) {
+  if (isset($element['#links']['entitywebformquickedit'])) {
+    $element['#links']['entitywebformquickedit']['attributes'] = WebformDialogHelper::getModalDialogAttributes();
+
+    // Place Quick link first.
+    // @todo Figure out why contextual link weight is not being respected.
+    $quick_edit_link = $element['#links']['entitywebformquickedit'];
+    unset($element['#links']['entitywebformquickedit']);
+    $element['#links'] = ['entitywebformquickedit' => $quick_edit_link] + $element['#links'];
+
+    $element['#attached']['library'][] = WebformDialogHelper::useOffCanvas() ? 'outside_in/drupal.off_canvas' : 'core/drupal.dialog.ajax';
+  }
+}
diff --git a/modules/webform_ui/webform_ui.routing.yml b/modules/webform_ui/webform_ui.routing.yml
index 68adc3e0..6fbb5b1f 100644
--- a/modules/webform_ui/webform_ui.routing.yml
+++ b/modules/webform_ui/webform_ui.routing.yml
@@ -1,3 +1,5 @@
+# Elements.
+
 entity.webform.source_form:
   path: '/admin/structure/webform/manage/{webform}/source'
   defaults:
@@ -6,6 +8,8 @@ entity.webform.source_form:
   requirements:
     _custom_access: '\Drupal\webform_ui\Access\WebformUiAccess::checkWebformSourceAccess'
 
+# Options.
+
 entity.webform_options.source_form:
   path: '/admin/structure/webform/settings/options/manage/{webform_options}/source'
   defaults:
@@ -14,6 +18,8 @@ entity.webform_options.source_form:
   requirements:
     _custom_access: '\Drupal\webform_ui\Access\WebformUiAccess::checkWebformOptionSourceAccess'
 
+# Element.
+
 entity.webform_ui.element:
   path: '/admin/structure/webform/manage/{webform}/element/add'
   defaults:
diff --git a/modules/webform_ui/webform_ui.services.yml b/modules/webform_ui/webform_ui.services.yml
new file mode 100755
index 00000000..bfa52d4b
--- /dev/null
+++ b/modules/webform_ui/webform_ui.services.yml
@@ -0,0 +1,5 @@
+services:
+  webform_ui.path_processor:
+    class: Drupal\webform_ui\PathProcessor\WebformUiPathProcessor
+    tags:
+      - {name: path_processor_outbound}
diff --git a/src/Utility/WebformYaml.php b/src/Utility/WebformYaml.php
index 8c04a1fd..c9ce46d7 100644
--- a/src/Utility/WebformYaml.php
+++ b/src/Utility/WebformYaml.php
@@ -70,7 +70,7 @@ class WebformYaml {
         }
 
         if (strpos($value, '<') === FALSE) {
-          $lines[$index] = $prefix . $name . ": |\n$indent  " . str_replace(PHP_EOL, "\n$indent  ", $value);
+          $lines[$index] = $prefix . $name . ": |\n$prefix  " . str_replace(PHP_EOL, "\n$prefix  ", $value);
         }
         else {
           $value = preg_replace('~\R~u', PHP_EOL, $value);
diff --git a/src/WebformDialogTrait.php b/src/WebformDialogTrait.php
index f55ec673..d3af60d8 100644
--- a/src/WebformDialogTrait.php
+++ b/src/WebformDialogTrait.php
@@ -13,6 +13,11 @@ use Drupal\webform\Ajax\ScrollTopCommand;
 
 /**
  * Trait class webform dialogs.
+ *
+ * @todo Issue #2862625: Rename offcanvas to two words in code and comments.
+ * @see https://www.drupal.org/node/2862625
+ * @todo Issue #2785047: In Outside In mode, messages should appear in the off-canvas tray, not the main page.
+ * @see https://www.drupal.org/node/2785047
  */
 trait WebformDialogTrait {
 
@@ -20,7 +25,7 @@ trait WebformDialogTrait {
    * Is the current request for an AJAX modal dialog.
    *
    * @return bool
-   *   TRUE is the current request if for an AJAX modal dialog.
+   *   TRUE if the current request is for an AJAX modal dialog.
    */
   protected function isModalDialog() {
     $wrapper_format = $this->getRequest()
@@ -33,6 +38,20 @@ trait WebformDialogTrait {
   }
 
   /**
+   * Is the current request for an off canvas dialog.
+   *
+   * @return bool
+   *   TRUE if the current request is for an off canvas dialog.
+   */
+  protected function isOffCanvasDialog() {
+    $wrapper_format = $this->getRequest()
+      ->get(MainContentViewSubscriber::WRAPPER_FORMAT);
+    return (in_array($wrapper_format, [
+      'drupal_dialog_offcanvas',
+    ])) ? TRUE : FALSE;
+  }
+
+  /**
    * Add modal dialog support to a form.
    *
    * @param array $form
@@ -70,7 +89,7 @@ trait WebformDialogTrait {
    *   The webform with modal dialog support.
    */
   protected function buildConfirmFormDialog(array &$form, FormStateInterface $form_state) {
-    if (!$this->isModalDialog()) {
+    if (!$this->isModalDialog() || $this->isOffCanvasDialog()) {
       return $form;
     }
 
@@ -121,8 +140,8 @@ trait WebformDialogTrait {
     }
     else {
       $response = new AjaxResponse();
-      if ($this->requestStack->getCurrentRequest()->get('destination')) {
-        $response->addCommand(new RedirectCommand($this->getRedirectDestination()->get()));
+      if ($path = $this->getRedirectDestinationPath()) {
+        $response->addCommand(new RedirectCommand(base_path() . $path));
       }
       elseif ($redirect_url = $this->getRedirectUrl()) {
         $response->addCommand(new RedirectCommand($redirect_url->toString()));
@@ -174,6 +193,33 @@ trait WebformDialogTrait {
    *   The redirect URL or NULL if dialog should just be closed.
    */
   protected function getRedirectUrl() {
+    return getDestinationUrl();
+  }
+
+  /**
+   * Get the URL from the destination service.
+   *
+   * @return \Drupal\Core\Url|null
+   *   The destination URL or NULL no destination available.
+   */
+  protected function getDestinationUrl() {
+    if ($destination = $this->getRedirectDestinationPath()) {
+      return Url::fromUserInput(base_path() . $destination);
+    }
+
+    return NULL;
+  }
+
+  /**
+   * Get the redirect destination path if specified in request.
+   *
+   * @return string|null
+   *   The redirect path or NULL if it is not specified.
+   */
+  protected function getRedirectDestinationPath() {
+    if ($this->requestStack->getCurrentRequest()->get('destination')) {
+      return $this->getRedirectDestination()->get();
+    }
     return NULL;
   }
 
diff --git a/src/WebformEntityForm.php b/src/WebformEntityForm.php
index 9410ec47..729384c3 100644
--- a/src/WebformEntityForm.php
+++ b/src/WebformEntityForm.php
@@ -265,6 +265,9 @@ class WebformEntityForm extends BundleEntityFormBase {
    * {@inheritdoc}
    */
   public function getRedirectUrl() {
+    if ($url = $this->getDestinationUrl()) {
+      return $url;
+    }
     return Url::fromRoute('entity.webform.edit_form', ['webform' => $this->getEntity()->id()]);
   }
 
diff --git a/webform.install b/webform.install
index 19d81cb6..3ed0a9db 100644
--- a/webform.install
+++ b/webform.install
@@ -6,6 +6,7 @@
  */
 
 use Drupal\Component\Render\FormattableMarkup;
+use Drupal\Core\Cache\Cache;
 use Drupal\Core\Serialization\Yaml;
 use Drupal\system\Entity\Action;
 use Drupal\webform\Entity\Webform;
@@ -393,3 +394,12 @@ function webform_update_8036() {
 function webform_update_8037() {
   \Drupal::token()->resetInfo();
 }
+
+/**
+ * Issue #286655: Add Quick Edit off canvas form.
+ */
+function webform_update_8038() {
+  // Copied from: outside_in_install()
+  Cache::invalidateTags(['rendered']);
+  \Drupal::service('cache.discovery')->deleteAll();
+}
diff --git a/webform.libraries.yml b/webform.libraries.yml
index 9641b568..6d7c8664 100644
--- a/webform.libraries.yml
+++ b/webform.libraries.yml
@@ -22,6 +22,7 @@ webform.admin.dialog:
     js/webform.dialog.js:  {}
   dependencies:
     - core/jquery.ui.dialog
+    - core/drupal.dialog.ajax
     - webform/webform.ajax
     - webform/webform.form
     - webform/webform.element.codemirror.yaml
