diff --git a/core/modules/outside_in/outside_in.links.contextual.yml b/core/modules/outside_in/outside_in.links.contextual.yml
index 99b36ef..62ca999 100644
--- a/core/modules/outside_in/outside_in.links.contextual.yml
+++ b/core/modules/outside_in/outside_in.links.contextual.yml
@@ -2,3 +2,6 @@ outside_in.block_configure:
   title: 'Quick edit'
   route_name: 'entity.block.offcanvas_form'
   group: 'block'
+  options:
+    dialog-type: dialog
+    dialog-renderer: offcanvas
diff --git a/core/modules/outside_in/outside_in.module b/core/modules/outside_in/outside_in.module
index a7fab7b..cf2aaf0 100644
--- a/core/modules/outside_in/outside_in.module
+++ b/core/modules/outside_in/outside_in.module
@@ -5,6 +5,7 @@
  * Allows configuring blocks and other configuration from the site front-end.
  */
 
+use Drupal\Component\Utility\Html;
 use Drupal\Core\Asset\AttachedAssetsInterface;
 use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\outside_in\Block\BlockEntityOffCanvasForm;
@@ -30,17 +31,28 @@ function outside_in_help($route_name, RouteMatchInterface $route_match) {
 /**
  * Implements hook_contextual_links_view_alter().
  *
- * Change Configure Blocks into offcanvas links.
+ * @todo Move to \Drupal\contextual\Element\ContextualLinks::preRenderLinks() in
++  *   https://www.drupal.org/node/2784443.
  */
 function outside_in_contextual_links_view_alter(&$element, $items) {
+  $add_library = FALSE;
+  foreach ($items as $class => $item) {
+    $class = Html::getClass($class);
+    if (isset($element['#links'][$class]) && !empty($item['localized_options']['dialog-type'])) {
+      $add_library = TRUE;
+      $element['#links'][$class]['attributes']['class'][] = 'use-ajax';
+      $element['#links'][$class]['attributes']['data-dialog-type'] = $item['localized_options']['dialog-type'];
+      if (!empty($item['localized_options']['dialog-renderer'])) {
+        $element['#links'][$class]['attributes']['data-dialog-renderer'] = $item['localized_options']['dialog-renderer'];
+      }
+    }
+  }
+  // This section should NOT be moved to
+  // \Drupal\contextual\Element\ContextualLinks::preRenderLinks().
   if (isset($element['#links']['outside-inblock-configure'])) {
-    $element['#links']['outside-inblock-configure']['attributes'] = [
-      'class' => ['use-ajax'],
-      'data-dialog-type' => 'dialog',
-      'data-dialog-renderer' => 'offcanvas',
-      'data-outside-in-edit' => TRUE,
-    ];
-
+    $element['#links']['outside-inblock-configure']['attributes']['data-outside-in-edit'] = TRUE;
+  }
+  if ($add_library) {
     $element['#attached']['library'][] = 'outside_in/drupal.off_canvas';
   }
 }
diff --git a/core/modules/outside_in/tests/src/Unit/ContextualLinksViewAlterTest.php b/core/modules/outside_in/tests/src/Unit/ContextualLinksViewAlterTest.php
new file mode 100644
index 0000000..17d4403
--- /dev/null
+++ b/core/modules/outside_in/tests/src/Unit/ContextualLinksViewAlterTest.php
@@ -0,0 +1,143 @@
+<?php
+
+namespace Drupal\Tests\outside_in\Unit;
+
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * Tests outside_in_contextual_links_view_alter().
+ *
+ * @group outside_in
+ */
+class ContextualLinksViewAlterTest extends UnitTestCase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['outside_in'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    require_once $this->root . '/core/modules/outside_in/outside_in.module';
+  }
+
+  /**
+   * @covers ::outside_in_contextual_links_view_alter
+   */
+  public function testValidDialogOptions() {
+    $elements = [
+      '#links' => [
+        'no-options' => [
+          'attributes' => [
+            'class' => [
+              'existing-class',
+            ],
+          ],
+        ],
+        'type' => [],
+        'type-and-renderer' => [],
+        'foo-barbaz-bop' => [],
+      ],
+    ];
+    $items = [
+      'no_options' => [
+        'route_name' => 'no_options',
+        'route_parameters' => [],
+      ],
+      'type' => [
+        'route_name' => 'type',
+        'route_parameters' => [],
+        'localized_options' => [
+          'dialog-type' => 'modal',
+        ],
+      ],
+      'type_and_renderer' => [
+        'route_name' => 'type_and_renderer',
+        'route_parameters' => [],
+        'localized_options' => [
+          'dialog-type' => 'modal',
+          'dialog-renderer' => 'offcanvas',
+        ],
+      ],
+      'foo-bar.baz_bop' => [
+        'route_name' => 'special_characters_in_link_name',
+        'route_parameters' => [],
+        'localized_options' => [],
+      ],
+    ];
+    $expected = [
+      '#links' => [
+        'no-options' => [
+          'attributes' => [
+            'class' => [
+              'existing-class',
+            ],
+          ],
+        ],
+        'type' => [
+          'attributes' => [
+            'class' => [
+              'use-ajax',
+            ],
+            'data-dialog-type' => 'modal',
+          ],
+        ],
+        'type-and-renderer' => [
+          'attributes' => [
+            'class' => [
+              'use-ajax',
+            ],
+            'data-dialog-type' => 'modal',
+            'data-dialog-renderer' => 'offcanvas',
+          ],
+        ],
+        'foo-barbaz-bop' => [
+        ],
+      ],
+      '#attached' => ['library' => ['outside_in/drupal.off_canvas']],
+    ];
+    outside_in_contextual_links_view_alter($elements, $items);
+    $this->assertEquals($expected, $elements);
+  }
+
+  /**
+   * @covers ::outside_in_contextual_links_view_alter
+   */
+  public function testNoDialogOptions() {
+    $elements = [
+      '#links' => [
+        'no-options' => [
+          'attributes' => [
+            'class' => [
+              'existing-class',
+            ],
+          ],
+        ],
+      ],
+    ];
+    $items = [
+      'no_options' => [
+        'route_name' => 'no_options',
+        'route_parameters' => [],
+      ],
+    ];
+    $expected = [
+      '#links' => [
+        'no-options' => [
+          'attributes' => [
+            'class' => [
+              'existing-class',
+            ],
+          ],
+        ],
+      ],
+    ];
+    outside_in_contextual_links_view_alter($elements, $items);
+    $this->assertEquals($expected, $elements);
+  }
+
+}
