diff --git a/README.md b/README.md
index ad85fcd8..35db66f6 100644
--- a/README.md
+++ b/README.md
@@ -36,7 +36,6 @@ The primary use case for this module is to:
 - [https://dev.acquia.com/blog/drupal-8-module-of-the-week--webform-formerly-known-as-yaml-form/07/03/2017/17741](https://dev.acquia.com/blog/drupal-8-module-of-the-week--webform-formerly-known-as-yaml-form/07/03/2017/17741)
 - [Drupal 8 Module Webform picks up speed](https://internetdevels.com/blog/drupal-8-module-webform)
 - [How to Make a Complex Webform in Drupal 8](https://www.ostraining.com/blog/drupal/how-to-make-a-complex-webform-in-drupal-8/)
-- [3 Reasons We Love the Webform Module in Drupal 8](https://www.unleashed-technologies.com/blog/2017/04/07/3-reasons-we-love-webform-module-drupal-8)
 - [Getting NYU onto Webform](https://www.fourkitchens.com/blog/article/getting-nyu-yaml-form)
 - [Webforms for Drupal 8](https://www.gaiaresources.com.au/yaml-forms-drupal-8/)
 - [Creating Webform Handlers in Drupal 8](http://fivemilemedia.co.uk/blog/creating-yaml-form-handlers-drupal-8)
diff --git a/css/webform.theme.bartik.css b/css/webform.theme.bartik.css
index e7efbd48..14a1ae35 100644
--- a/css/webform.theme.bartik.css
+++ b/css/webform.theme.bartik.css
@@ -3,7 +3,47 @@
  * Bartik theme styles.
  */
 
-/* Add background color to table cells so that very wide off-screen tables look okay. */
+/**
+ * Add background color to table cells so that very wide off-screen tables look okay.
+ */
 table {
   background-color: #fff;
 }
+
+/**
+ * Tweak webform (elements) form.
+ */
+.webform-form .tableresponsive-toggle-columns,
+.webform-form .tabledrag-toggle-weight-wrapper {
+  display: none;
+}
+
+/**
+ * Tweak webform element form.
+ */
+.webform-states-table th:last-child,
+.webform-states-table td:last-child {
+  min-width: 65px;
+}
+
+.webform-states-table td:last-child input {
+  margin: 0;
+}
+
+.webform-multiple-table td:last-child,
+.webform-multiple-table td:last-child {
+  min-width: 65px;
+}
+
+.webform-multiple-table td:last-child input {
+  margin: 0;
+}
+
+/**
+ * Tweak off-canvas webform element form.
+ */
+/* @todo Issue #2862625: Rename offcanvas to two words in code and comments. https://www.drupal.org/node/2862625 */
+.ui-dialog-offcanvas .webform-ui-element-form details > .details-wrapper,
+.ui-dialog-off-canvas .webform-ui-element-form details > .details-wrapper{
+  padding: 0 .5em;
+}
diff --git a/css/webform.theme.seven.css b/css/webform.theme.seven.css
index b7c7b926..b4f36189 100644
--- a/css/webform.theme.seven.css
+++ b/css/webform.theme.seven.css
@@ -46,18 +46,22 @@ details details details {
 }
 
 /* System tray divider */
-.ui-dialog.ui-dialog-offcanvas .ui-resizable-w {
+/* @todo Issue #2862625: Rename offcanvas to two words in code and comments. https://www.drupal.org/node/2862625 */
+.ui-dialog.ui-dialog-offcanvas .ui-resizable-w,
+.ui-dialog.ui-dialog-off-canvas .ui-resizable-w {
   background-color: #bfbfba;
   border: 1px solid #6b6b6b;
   border-width: 1px 2px;
 }
 
 /* System tray title bar */
-.ui-dialog.ui-dialog-offcanvas .ui-dialog-titlebar {
+.ui-dialog.ui-dialog-offcanvas .ui-dialog-titlebar,
+.ui-dialog.ui-dialog-off-canvas .ui-dialog-titlebar{
   border-radius: 0;
 }
 
 /* System tray actions */
+.ui-dialog.ui-dialog-off-canvas .ui-dialog-content .form-actions
 .ui-dialog.ui-dialog-offcanvas .ui-dialog-content .form-actions {
   margin: 1em 0;
 }
diff --git a/drush/webform.drush.inc b/drush/webform.drush.inc
index 91ba4c19..502a4d4b 100644
--- a/drush/webform.drush.inc
+++ b/drush/webform.drush.inc
@@ -690,6 +690,13 @@ function drush_webform_repair() {
 
   drush_print('Repairing webform submission storage schema...');
    _webform_update_webform_submission_storage_schema();
+
+  // Copied from: outside_in_install().
+  drush_print('Repairing quick links...');
+  // @todo Remove in https://www.drupal.org/node/2783791.
+  Cache::invalidateTags(['rendered']);
+  // @todo Remove when that is fixed in https://www.drupal.org/node/2773591.
+  \Drupal::service('cache.discovery')->deleteAll();
 }
 
 /******************************************************************************/
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..e2b0cc4a 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->getRedirectDestinationUrl()) {
+      return $url;
+    }
     return $this->webform->toUrl('edit-form', ['query' => ['element-update' => $this->key]]);
   }
 
diff --git a/modules/webform_ui/src/Form/WebformUiElementTypeChangeForm.php b/modules/webform_ui/src/Form/WebformUiElementTypeChangeForm.php
index 16a048e7..ff870926 100644
--- a/modules/webform_ui/src/Form/WebformUiElementTypeChangeForm.php
+++ b/modules/webform_ui/src/Form/WebformUiElementTypeChangeForm.php
@@ -34,11 +34,12 @@ class WebformUiElementTypeChangeForm extends WebformUiElementTypeFormBase {
       throw new NotFoundHttpException();
     }
 
-    $headers = [
-      ['data' => $this->t('Element')],
-      ['data' => $this->t('Category')],
-      ['data' => $this->t('Operations')],
-    ];
+    $headers = [];
+    $headers[] = ['data' => $this->t('Element')];
+    $headers[] = ['data' => $this->t('Category')];
+    if (!$this->isOffCanvasDialog()) {
+      $headers[] = ['data' => $this->t('Operations')];
+    }
 
     $definitions = $this->getDefinitions();
     $rows = [];
@@ -53,25 +54,29 @@ class WebformUiElementTypeChangeForm extends WebformUiElementTypeFormBase {
         '#attributes' => WebformDialogHelper::getModalDialogAttributes(800, ['webform-tooltip-link', 'js-webform-tooltip-link']) + ['title' => $plugin_definition['description']],
       ];
       $row['category']['data'] = (isset($plugin_definition['category'])) ? $plugin_definition['category'] : $this->t('Other');
-      $row['operations']['data'] = [
-        '#type' => 'operations',
-        '#links' => [
-          'change' => [
-            'title' => $this->t('Change'),
-            'url' => Url::fromRoute('entity.webform_ui.element.edit_form', ['webform' => $webform->id(), 'key' => $key], ['query' => ['type' => $related_type_name]]),
-            'attributes' => WebformDialogHelper::getModalDialogAttributes(800),
+      if (!$this->isOffCanvasDialog()) {
+        $row['operations']['data'] = [
+          '#type' => 'operations',
+          '#links' => [
+            'change' => [
+              'title' => $this->t('Change'),
+              'url' => Url::fromRoute('entity.webform_ui.element.edit_form', [
+                'webform' => $webform->id(),
+                'key' => $key
+              ], ['query' => ['type' => $related_type_name]]),
+              'attributes' => WebformDialogHelper::getModalDialogAttributes(800),
+            ],
           ],
-        ],
-      ];
+        ];
 
-      // 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 ($related_type_name == 'processed_text') {
-        unset($row['operations']['data']['#links']['change']['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 ($related_type_name == 'processed_text') {
+          unset($row['operations']['data']['#links']['change']['attributes']);
+        }
       }
-
       $rows[] = $row;
     }
 
diff --git a/modules/webform_ui/src/Form/WebformUiElementTypeFormBase.php b/modules/webform_ui/src/Form/WebformUiElementTypeFormBase.php
index 183c6767..567d62dd 100644
--- a/modules/webform_ui/src/Form/WebformUiElementTypeFormBase.php
+++ b/modules/webform_ui/src/Form/WebformUiElementTypeFormBase.php
@@ -4,6 +4,7 @@ namespace Drupal\webform_ui\Form;
 
 use Drupal\Core\Form\FormBase;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\webform\WebformDialogTrait;
 use Drupal\webform\WebformElementManagerInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
@@ -12,6 +13,8 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
  */
 abstract class WebformUiElementTypeFormBase extends FormBase {
 
+  use WebformDialogTrait;
+
   /**
    * The webform element manager.
    *
diff --git a/modules/webform_ui/src/Form/WebformUiElementTypeSelectForm.php b/modules/webform_ui/src/Form/WebformUiElementTypeSelectForm.php
index b6ecc5bd..6596c321 100644
--- a/modules/webform_ui/src/Form/WebformUiElementTypeSelectForm.php
+++ b/modules/webform_ui/src/Form/WebformUiElementTypeSelectForm.php
@@ -25,11 +25,12 @@ class WebformUiElementTypeSelectForm extends WebformUiElementTypeFormBase {
   public function buildForm(array $form, FormStateInterface $form_state, WebformInterface $webform = NULL) {
     $parent = $this->getRequest()->query->get('parent');
 
-    $headers = [
-      ['data' => $this->t('Element')],
-      ['data' => $this->t('Category')],
-      ['data' => $this->t('Operations')],
-    ];
+    $headers = [];
+    $headers[] = ['data' => $this->t('Element')];
+    $headers[] = ['data' => $this->t('Category')];
+    if (!$this->isOffCanvasDialog()) {
+      $headers[] = ['data' => $this->t('Operations')];
+    }
 
     $elements = $this->elementManager->getInstances();
     $definitions = $this->getDefinitions();
@@ -60,16 +61,18 @@ class WebformUiElementTypeSelectForm extends WebformUiElementTypeFormBase {
         '#suffix' => '</div>',
       ];
       $row['category']['data'] = $plugin_definition['category'];
-      $row['operations']['data'] = [
-        '#type' => 'operations',
-        '#links' => [
-          'add' => [
-            'title' => $this->t('Add element'),
-            'url' => Url::fromRoute('entity.webform_ui.element.add_form', $route_parameters, $route_options),
-            'attributes' => WebformDialogHelper::getModalDialogAttributes(800),
+      if (!$this->isOffCanvasDialog()) {
+        $row['operations']['data'] = [
+          '#type' => 'operations',
+          '#links' => [
+            'add' => [
+              'title' => $this->t('Add element'),
+              'url' => Url::fromRoute('entity.webform_ui.element.add_form', $route_parameters, $route_options),
+              'attributes' => WebformDialogHelper::getModalDialogAttributes(800),
+            ],
           ],
-        ],
-      ];
+        ];
+      }
       // 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.
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..ad4788eb 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,259 @@ 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');
+    if ($webform->hasContainer()) {
+      $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 ($webform->hasContainer()) {
+      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..6f179c02 100644
--- a/modules/webform_ui/webform_ui.module
+++ b/modules/webform_ui/webform_ui.module
@@ -5,6 +5,9 @@
  * Provides a simple user interface for building and maintaining webforms.
  */
 
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\webform\Utility\WebformDialogHelper;
+
 /**
  * Implements hook_entity_type_alter().
  */
@@ -20,6 +23,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']);
   }
 
@@ -35,5 +39,42 @@ function webform_ui_entity_type_alter(array &$entity_types) {
     $handlers['form']['default'] = 'Drupal\webform_ui\WebformUiOptionsForm';
     $webform_options_entity_type->setHandlerClass('form', $handlers['form']);
   }
+}
+
+/**
+ * Implements hook_webform_submission_form_alter().
+ */
+function webform_ui_webform_submission_form_alter(array &$form, FormStateInterface $form_state, $form_id) {
+  // Attach dialog libraries to editable webforms to make sure that the
+  // quickedit dialog/system tray works as expected.
+  if (\Drupal::moduleHandler()->moduleExists('quickedit')) {
+    /** @var \Drupal\webform\WebformSubmissionForm $form_object */
+    $form_object = $form_state->getFormObject();
+    if ($form_object->getEntity()->access('update')) {
+      WebformDialogHelper::attachLibraries($form);
+    }
+  }
+}
+
+/**
+ * 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/Element/WebformElementStates.php b/src/Element/WebformElementStates.php
index e722f01b..0ebacac8 100644
--- a/src/Element/WebformElementStates.php
+++ b/src/Element/WebformElementStates.php
@@ -192,7 +192,7 @@ class WebformElementStates extends FormElement {
     ];
 
     $element['#attached']['library'][] = 'webform/webform.element.states';
-    $element['#attached']['library'][] = 'webform/webform.element.select2';
+    // $element['#attached']['library'][] = 'webform/webform.element.select2';
 
     return $element;
   }
@@ -227,7 +227,7 @@ class WebformElementStates extends FormElement {
       '#default_value' => $state['state'],
       '#empty_option' => '',
       '#empty_value' => '',
-      '#attributes' => ['class' => ['js-webform-select2', 'webform-select2']],
+      // '#attributes' => ['class' => ['js-webform-select2', 'webform-select2']],
     ];
     $row['operator'] = [
       '#type' => 'select',
@@ -279,7 +279,7 @@ class WebformElementStates extends FormElement {
       '#default_value' => $condition['selector'],
       '#empty_option' => '',
       '#empty_value' => '',
-      '#attributes' => ['class' => ['js-webform-select2', 'webform-select2']],
+      // '#attributes' => ['class' => ['js-webform-select2', 'webform-select2']],
     ];
     $row['trigger'] = [
       '#type' => 'select',
@@ -287,7 +287,7 @@ class WebformElementStates extends FormElement {
       '#default_value' => $condition['trigger'],
       '#empty_option' => '',
       '#empty_value' => '',
-      '#attributes' => ['class' => ['js-webform-select2', 'webform-select2']],
+      // '#attributes' => ['class' => ['js-webform-select2', 'webform-select2']],
     ];
     $row['value'] = [
       '#type' => 'textfield',
diff --git a/src/Element/WebformOptions.php b/src/Element/WebformOptions.php
index ac742649..47bba4d5 100644
--- a/src/Element/WebformOptions.php
+++ b/src/Element/WebformOptions.php
@@ -94,14 +94,12 @@ class WebformOptions extends FormElement {
             '#title' => t('Option value'),
             '#title_display' => t('invisible'),
             '#placeholder' => t('Enter value'),
-            '#maxlength' => 255,
           ],
           'text' => [
             '#type' => 'textfield',
             '#title' => t('Option text'),
             '#title_display' => t('invisible'),
             '#placeholder' => t('Enter text'),
-            '#maxlength' => 255,
           ],
         ],
         '#default_value' => (isset($element['#default_value'])) ? self::convertOptionsToValues($element['#default_value']) : [],
diff --git a/src/Entity/Webform.php b/src/Entity/Webform.php
index 8c2e74bb..e69ac8cd 100644
--- a/src/Entity/Webform.php
+++ b/src/Entity/Webform.php
@@ -288,6 +288,13 @@ class Webform extends ConfigEntityBundleBase implements WebformInterface {
   protected $hasFlexboxLayout = FALSE;
 
   /**
+   * Track if the webform has container.
+   *
+   * @var bool
+   */
+  protected $hasContainer = FALSE;
+
+  /**
    * Track if the webform has translations.
    *
    * @var bool
@@ -502,6 +509,14 @@ class Webform extends ConfigEntityBundleBase implements WebformInterface {
   /**
    * {@inheritdoc}
    */
+  public function hasContainer() {
+    $this->initElements();
+    return $this->hasContainer;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function getDescription() {
     return $this->description;
   }
@@ -925,6 +940,7 @@ class Webform extends ConfigEntityBundleBase implements WebformInterface {
 
     $this->hasManagedFile = FALSE;
     $this->hasFlexboxLayout = FALSE;
+    $this->hasContainer = FALSE;
     $this->elementsDecodedAndFlattened = [];
     $this->elementsInitializedAndFlattened = [];
     $this->elementsInitializedFlattenedAndHasValue = [];
@@ -976,6 +992,7 @@ class Webform extends ConfigEntityBundleBase implements WebformInterface {
     $this->pages = NULL;
     $this->hasManagedFile = NULL;
     $this->hasFlexboxLayout = NULL;
+    $this->hasContainer = NULL;
     $this->elementsDecoded = NULL;
     $this->elementsInitialized = NULL;
     $this->elementsDecodedAndFlattened = NULL;
@@ -1065,6 +1082,11 @@ class Webform extends ConfigEntityBundleBase implements WebformInterface {
           $this->hasFlexboxLayout = TRUE;
         }
 
+        // Track container.
+        if ($element_handler->isContainer($element)) {
+          $this->hasContainer = TRUE;
+        }
+
         $element['#webform_multiple'] = $element_handler->hasMultipleValues($element);
         $element['#webform_composite'] = $element_handler->isComposite();
       }
diff --git a/src/Utility/WebformDialogHelper.php b/src/Utility/WebformDialogHelper.php
index f3114a5c..aa44e3eb 100644
--- a/src/Utility/WebformDialogHelper.php
+++ b/src/Utility/WebformDialogHelper.php
@@ -3,7 +3,6 @@
 namespace Drupal\webform\Utility;
 
 use Drupal\Component\Serialization\Json;
-use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
 
 /**
  * Helper class for dialog methods.
@@ -11,6 +10,49 @@ use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
 class WebformDialogHelper {
 
   /**
+   * Off canvas trigger name.
+   *
+   * @var string
+   */
+  protected static $offCanvasTriggerName;
+
+  /**
+   * Get Off canvas trigger name.
+   *
+   * @return string
+   *   The off canvas trigger name.
+   *
+   * @see Issue #2862625: Rename offcanvas to two words in code and comments.
+   * @see https://www.drupal.org/node/2862625
+   */
+  public static function getOffCanvasTriggerName() {
+    if (isset(self::$offCanvasTriggerName)) {
+      return self::$offCanvasTriggerName;
+    }
+
+    $main_content_renderers = \Drupal::getContainer()->getParameter('main_content_renderers');
+
+    if (isset($main_content_renderers['drupal_dialog_offcanvas'])) {
+      self::$offCanvasTriggerName = 'offcanvas';
+    }
+    else {
+      self::$offCanvasTriggerName = 'off_canvas';
+    }
+
+    return self::$offCanvasTriggerName;
+  }
+
+  /**
+   * Use outside-in off-canvas system tray instead of dialogs.
+   *
+   * @return bool
+   *   TRUE if outside_in.module is enabled and system trays are not disabled.
+   */
+  public static function useOffCanvas() {
+    return ((floatval(\Drupal::VERSION) >= 8.3) && \Drupal::moduleHandler()->moduleExists('outside_in') && !\Drupal::config('webform.settings')->get('ui.offcanvas_disabled')) ? TRUE : FALSE;
+  }
+
+  /**
    * Attach libraries required by (modal) dialogs.
    *
    * @param array $build
@@ -46,9 +88,11 @@ class WebformDialogHelper {
         return [
           'class' => $class,
           'data-dialog-type' => 'dialog',
-          'data-dialog-renderer' => 'offcanvas',
+          'data-dialog-renderer' => self::getOffCanvasTriggerName(),
           'data-dialog-options' => Json::encode([
             'width' => ($width > 480) ? 480 : $width,
+            // @todo Decide if we want to use 'Outside In' custom system tray styling.
+            // 'dialogClass' => 'ui-dialog-outside-in',
           ]),
         ];
       }
@@ -64,14 +108,4 @@ class WebformDialogHelper {
     }
   }
 
-  /**
-   * Use outside-in off-canvas system tray instead of dialogs.
-   *
-   * @return bool
-   *   TRUE if outside_in.module is enabled and system trays are not disabled.
-   */
-  public static function useOffCanvas() {
-    return ((floatval(\Drupal::VERSION) >= 8.3) && \Drupal::moduleHandler()->moduleExists('outside_in') && !\Drupal::config('webform.settings')->get('ui.offcanvas_disabled')) ? TRUE : FALSE;
-  }
-
 }
diff --git a/src/WebformDialogTrait.php b/src/WebformDialogTrait.php
index 70557c9b..864a7ede 100644
--- a/src/WebformDialogTrait.php
+++ b/src/WebformDialogTrait.php
@@ -8,10 +8,15 @@ use Drupal\Core\Ajax\CloseDialogCommand;
 use Drupal\Core\Ajax\RedirectCommand;
 use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Url;
 use Drupal\webform\Ajax\ScrollTopCommand;
+use Drupal\webform\Utility\WebformDialogHelper;
 
 /**
  * Trait class webform dialogs.
+ *
+ * @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 {
 
@@ -19,7 +24,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()
@@ -27,7 +32,22 @@ trait WebformDialogTrait {
     return (in_array($wrapper_format, [
       'drupal_ajax',
       'drupal_modal',
-      'drupal_dialog_offcanvas',
+      'drupal_dialog',
+      'drupal_dialog_' . WebformDialogHelper::getOffCanvasTriggerName(),
+    ])) ? TRUE : FALSE;
+  }
+
+  /**
+   * 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_' . WebformDialogHelper::getOffCanvasTriggerName(),
     ])) ? TRUE : FALSE;
   }
 
@@ -69,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;
     }
 
@@ -120,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()));
@@ -169,6 +189,33 @@ trait WebformDialogTrait {
    *   The redirect URL or NULL if dialog should just be closed.
    */
   protected function getRedirectUrl() {
+    return getDestinationUrl();
+  }
+
+  /**
+   * Get the current request's redirect destination URL.
+   *
+   * @return \Drupal\Core\Url|null
+   *   The current request's redirect destination or NULL if no
+   *   destination available.
+   */
+  protected function getRedirectDestinationUrl() {
+    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 ed85e32b..c7431469 100644
--- a/src/WebformEntityForm.php
+++ b/src/WebformEntityForm.php
@@ -260,6 +260,9 @@ class WebformEntityForm extends BundleEntityFormBase {
    * {@inheritdoc}
    */
   public function getRedirectUrl() {
+    if ($url = $this->getRedirectDestinationUrl()) {
+      return $url;
+    }
     return Url::fromRoute('entity.webform.edit_form', ['webform' => $this->getEntity()->id()]);
   }
 
diff --git a/src/WebformInterface.php b/src/WebformInterface.php
index af4a10e3..8757cb07 100644
--- a/src/WebformInterface.php
+++ b/src/WebformInterface.php
@@ -76,11 +76,19 @@ interface WebformInterface extends ConfigEntityInterface, EntityWithPluginCollec
    * Determine if the webform is using a Flexbox layout.
    *
    * @return bool
-   *   TRUE if if the webform is using a Flexbox layout.
+   *   TRUE if the webform is using a Flexbox layout.
    */
   public function hasFlexboxLayout();
 
   /**
+   * Determine if the webform has any containers.
+   *
+   * @return bool
+   *   TRUE if the webform has any containers.
+   */
+  public function hasContainer();
+
+  /**
    * Sets the status of the configuration entity.
    *
    * @param string|bool|null $status
diff --git a/webform.install b/webform.install
index c8bd0067..2dbd28ae 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;
@@ -427,3 +428,12 @@ function webform_update_8039() {
     }
   }
 }
+
+/**
+ * Issue #286655: Add Quick Edit off canvas form.
+ */
+function webform_update_8040() {
+  // Copied from: outside_in_install()
+  Cache::invalidateTags(['rendered']);
+  \Drupal::service('cache.discovery')->deleteAll();
+}
diff --git a/webform.libraries.yml b/webform.libraries.yml
index 94e360e4..64c1cb75 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
diff --git a/webform.module b/webform.module
index 38b0db35..bfe10f38 100644
--- a/webform.module
+++ b/webform.module
@@ -254,6 +254,14 @@ function _webform_form_after_build($form, FormStateInterface $form_state) {
 }
 
 /**
+ * Implements hook_webform_submission_form_alter().
+ */
+function webform_webform_submission_form_alter(array &$form, FormStateInterface $form_state, $form_id) {
+  // Make webform libraries are always attached to submission form.
+  _webform_page_attachments($form);
+}
+
+/**
  * Implements hook_system_breadcrumb_alter().
  */
 function webform_system_breadcrumb_alter(Breadcrumb &$breadcrumb, RouteMatchInterface $route_match, array $context) {
@@ -370,19 +378,7 @@ function webform_page_attachments(array &$attachments) {
 
   // Attach global libraries only to webform specific pages.
   if (preg_match('/^(webform\.|^entity\.([^.]+\.)?webform)/', $route_name) || preg_match('#(/node/add/webform|/admin/help/webform)#', $url)) {
-    $active_theme_names = WebformThemeHelper::getActiveThemeNames();
-    foreach ($active_theme_names as $active_theme_name) {
-      if (file_exists(drupal_get_path('module', 'webform') . "/css/webform.theme.$active_theme_name.css")) {
-        $attachments['#attached']['library'][] = "webform/webform.theme.$active_theme_name";
-      }
-    }
-
-    // Attach details element save open/close library.
-    // This ensures pages without a webform will still be able to save the
-    // details element state.
-    if (\Drupal::config('webform.settings')->get('ui.details_save')) {
-      $attachments['#attached']['library'][] = 'webform/webform.element.details.save';
-    }
+    _webform_page_attachments($attachments);
   }
 
   // Attach codemirror and select2 library to block admin to ensure that the
@@ -392,6 +388,22 @@ function webform_page_attachments(array &$attachments) {
   }
 }
 
+function _webform_page_attachments(array &$attachments) {
+  $active_theme_names = WebformThemeHelper::getActiveThemeNames();
+  foreach ($active_theme_names as $active_theme_name) {
+    if (file_exists(drupal_get_path('module', 'webform') . "/css/webform.theme.$active_theme_name.css")) {
+      $attachments['#attached']['library'][] = "webform/webform.theme.$active_theme_name";
+    }
+  }
+
+  // Attach details element save open/close library.
+  // This ensures pages without a webform will still be able to save the
+  // details element state.
+  if (\Drupal::config('webform.settings')->get('ui.details_save')) {
+    $attachments['#attached']['library'][] = 'webform/webform.element.details.save';
+  }
+}
+
 /**
  * Implements hook_css_alter().
  *
