diff --git a/core/misc/ajax.es6.js b/core/misc/ajax.es6.js
index daa62548a0..2bb06962ef 100644
--- a/core/misc/ajax.es6.js
+++ b/core/misc/ajax.es6.js
@@ -60,6 +60,7 @@
           element_settings.event = 'click';
         }
         element_settings.dialogType = $(this).data('dialog-type');
+        element_settings.drupalDialogRenderer = $(this).data('dialog-renderer');
         element_settings.dialog = $(this).data('dialog-options');
         element_settings.base = $(this).attr('id');
         element_settings.element = this;
@@ -517,7 +518,13 @@
     else {
       ajax.options.url += '&';
     }
-    ajax.options.url += `${Drupal.ajax.WRAPPER_FORMAT}=drupal_${element_settings.dialogType || 'ajax'}`;
+    // If this element has a dialog type use if for the wrapper if not use 'ajax'.
+    let wrapper = `drupal_${(element_settings.dialogType || 'ajax')}`;
+    if (element_settings.drupalDialogRenderer) {
+      wrapper += `.${element_settings.drupalDialogRenderer}`;
+    }
+    ajax.options.url += `${Drupal.ajax.WRAPPER_FORMAT}=${wrapper}`;
+
 
     // Bind the ajaxSubmit function to the element event.
     $(ajax.element).on(element_settings.event, function (event) {
diff --git a/core/misc/ajax.js b/core/misc/ajax.js
index 1e15175edd..b84cfb1964 100644
--- a/core/misc/ajax.js
+++ b/core/misc/ajax.js
@@ -38,6 +38,7 @@ function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr
           element_settings.event = 'click';
         }
         element_settings.dialogType = $(this).data('dialog-type');
+        element_settings.drupalDialogRenderer = $(this).data('dialog-renderer');
         element_settings.dialog = $(this).data('dialog-options');
         element_settings.base = $(this).attr('id');
         element_settings.element = this;
@@ -240,7 +241,12 @@ function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr
     } else {
       ajax.options.url += '&';
     }
-    ajax.options.url += Drupal.ajax.WRAPPER_FORMAT + '=drupal_' + (element_settings.dialogType || 'ajax');
+
+    var wrapper = 'drupal_' + (element_settings.dialogType || 'ajax');
+    if (element_settings.drupalDialogRenderer) {
+      wrapper += '.' + element_settings.drupalDialogRenderer;
+    }
+    ajax.options.url += Drupal.ajax.WRAPPER_FORMAT + '=' + wrapper;
 
     $(ajax.element).on(element_settings.event, function (event) {
       if (!drupalSettings.ajaxTrustedUrl[ajax.url] && !Drupal.url.isLocal(ajax.url)) {
diff --git a/core/modules/outside_in/js/outside_in.es6.js b/core/modules/outside_in/js/outside_in.es6.js
index 66708cea0d..76bb872749 100644
--- a/core/modules/outside_in/js/outside_in.es6.js
+++ b/core/modules/outside_in/js/outside_in.es6.js
@@ -209,26 +209,14 @@
   Drupal.behaviors.toggleEditMode = {
     attach() {
       $(toggleEditSelector).once('outsidein').on('click.outsidein', toggleEditMode);
-
-      const search = `${Drupal.ajax.WRAPPER_FORMAT}=drupal_dialog`;
-      const replace = `${Drupal.ajax.WRAPPER_FORMAT}=drupal_dialog_off_canvas`;
-      // Loop through all Ajax links and change the format to dialog-off-canvas when
-      // needed.
+      // Find all Ajax instances that use the 'off_canvas' renderer.
       Drupal.ajax.instances
-        .filter((instance) => {
-          const hasElement = instance && !!instance.element;
-          let rendererOffCanvas = false;
-          let wrapperOffCanvas = false;
-          if (hasElement) {
-            rendererOffCanvas = $(instance.element).attr('data-dialog-renderer') === 'off_canvas';
-            wrapperOffCanvas = instance.options.url.indexOf('drupal_dialog_off_canvas') === -1;
-          }
-          return hasElement && rendererOffCanvas && wrapperOffCanvas;
-        })
+        // If there is an element and the renderer is 'off_canvas' then we want
+        // to add our changes.
+        .filter(instance => $(instance.element).attr('data-dialog-renderer') === 'off_canvas')
+        // Loop through all Ajax instances that use the 'off_canvas' renderer to
+        // set active editable ID.
         .forEach((instance) => {
-          // @todo Move logic for data-dialog-renderer attribute into ajax.js
-          //   https://www.drupal.org/node/2784443
-          instance.options.url = instance.options.url.replace(search, replace);
           // Check to make sure existing dialogOptions aren't overridden.
           if (!('dialogOptions' in instance.options.data)) {
             instance.options.data.dialogOptions = {};
diff --git a/core/modules/outside_in/js/outside_in.js b/core/modules/outside_in/js/outside_in.js
index 635196d88f..95cf90e225 100644
--- a/core/modules/outside_in/js/outside_in.js
+++ b/core/modules/outside_in/js/outside_in.js
@@ -125,21 +125,9 @@
     attach: function attach() {
       $(toggleEditSelector).once('outsidein').on('click.outsidein', toggleEditMode);
 
-      var search = Drupal.ajax.WRAPPER_FORMAT + '=drupal_dialog';
-      var replace = Drupal.ajax.WRAPPER_FORMAT + '=drupal_dialog_off_canvas';
-
       Drupal.ajax.instances.filter(function (instance) {
-        var hasElement = instance && !!instance.element;
-        var rendererOffCanvas = false;
-        var wrapperOffCanvas = false;
-        if (hasElement) {
-          rendererOffCanvas = $(instance.element).attr('data-dialog-renderer') === 'off_canvas';
-          wrapperOffCanvas = instance.options.url.indexOf('drupal_dialog_off_canvas') === -1;
-        }
-        return hasElement && rendererOffCanvas && wrapperOffCanvas;
+        return $(instance.element).attr('data-dialog-renderer') === 'off_canvas';
       }).forEach(function (instance) {
-        instance.options.url = instance.options.url.replace(search, replace);
-
         if (!('dialogOptions' in instance.options.data)) {
           instance.options.data.dialogOptions = {};
         }
diff --git a/core/modules/outside_in/outside_in.services.yml b/core/modules/outside_in/outside_in.services.yml
index 37ba1b351a..a53ce2938d 100644
--- a/core/modules/outside_in/outside_in.services.yml
+++ b/core/modules/outside_in/outside_in.services.yml
@@ -1,9 +1,9 @@
 services:
   main_content_renderer.off_canvas:
-    class: Drupal\outside_in\Render\MainContent\OffCanvasRender
+    class: Drupal\outside_in\Render\MainContent\OffCanvasRenderer
     arguments: ['@title_resolver', '@renderer']
     tags:
-      - { name: render.main_content_renderer, format: drupal_dialog_off_canvas }
+      - { name: render.main_content_renderer, format: drupal_dialog.off_canvas }
 
   outside_in.manager:
     class: Drupal\outside_in\OutsideInManager
diff --git a/core/modules/outside_in/src/Render/MainContent/OffCanvasRender.php b/core/modules/outside_in/src/Render/MainContent/OffCanvasRenderer.php
similarity index 95%
rename from core/modules/outside_in/src/Render/MainContent/OffCanvasRender.php
rename to core/modules/outside_in/src/Render/MainContent/OffCanvasRenderer.php
index 9119c9b8cf..3286ed9f4b 100644
--- a/core/modules/outside_in/src/Render/MainContent/OffCanvasRender.php
+++ b/core/modules/outside_in/src/Render/MainContent/OffCanvasRenderer.php
@@ -13,7 +13,7 @@
 /**
  * Default main content renderer for off-canvas dialog requests.
  */
-class OffCanvasRender extends DialogRenderer {
+class OffCanvasRenderer extends DialogRenderer {
 
   /**
    * The renderer.
@@ -23,7 +23,7 @@ class OffCanvasRender extends DialogRenderer {
   protected $renderer;
 
   /**
-   * Constructs a new OffCanvasRender.
+   * Constructs a new OffCanvasRenderer.
    *
    * @param \Drupal\Core\Controller\TitleResolverInterface $title_resolver
    *   The title resolver.
diff --git a/core/modules/outside_in/src/Tests/Ajax/OffCanvasDialogTest.php b/core/modules/outside_in/src/Tests/Ajax/OffCanvasDialogTest.php
index e2b451e1a7..edf055b577 100644
--- a/core/modules/outside_in/src/Tests/Ajax/OffCanvasDialogTest.php
+++ b/core/modules/outside_in/src/Tests/Ajax/OffCanvasDialogTest.php
@@ -53,7 +53,7 @@ public function testDialog() {
     ];
 
     // Emulate going to the JS version of the page and check the JSON response.
-    $ajax_result = $this->drupalGetAjax('ajax-test/dialog-contents', ['query' => [MainContentViewSubscriber::WRAPPER_FORMAT => 'drupal_dialog_off_canvas']]);
+    $ajax_result = $this->drupalGetAjax('ajax-test/dialog-contents', ['query' => [MainContentViewSubscriber::WRAPPER_FORMAT => 'drupal_dialog.off_canvas']]);
     $this->assertEqual($off_canvas_expected_response, $ajax_result[3], 'off-canvas dialog JSON response matches.');
   }
 
diff --git a/core/modules/system/tests/modules/dialog_renderer_test/dialog_renderer_test.info.yml b/core/modules/system/tests/modules/dialog_renderer_test/dialog_renderer_test.info.yml
new file mode 100644
index 0000000000..8dde142ae3
--- /dev/null
+++ b/core/modules/system/tests/modules/dialog_renderer_test/dialog_renderer_test.info.yml
@@ -0,0 +1,6 @@
+name: 'Dialog Renderer Test'
+type: module
+description: 'Support module for Dialog Renderer tests.'
+core: 8.x
+package: Testing
+version: VERSION
diff --git a/core/modules/system/tests/modules/dialog_renderer_test/dialog_renderer_test.routing.yml b/core/modules/system/tests/modules/dialog_renderer_test/dialog_renderer_test.routing.yml
new file mode 100644
index 0000000000..3cfbde0d7e
--- /dev/null
+++ b/core/modules/system/tests/modules/dialog_renderer_test/dialog_renderer_test.routing.yml
@@ -0,0 +1,15 @@
+dialog_renderer_test.links:
+  path: '/dialog_renderer-test-links'
+  defaults:
+    _controller: '\Drupal\dialog_renderer_test\Controller\TestController::linksDisplay'
+    _title: 'Links'
+  requirements:
+    _access: 'TRUE'
+
+dialog_renderer_test.modal_content:
+  path: '/dialog_renderer-content'
+  defaults:
+    _controller: '\Drupal\dialog_renderer_test\Controller\TestController::modalContent'
+    _title: 'Thing 1'
+  requirements:
+    _access: 'TRUE'
diff --git a/core/modules/system/tests/modules/dialog_renderer_test/dialog_renderer_test.services.yml b/core/modules/system/tests/modules/dialog_renderer_test/dialog_renderer_test.services.yml
new file mode 100644
index 0000000000..7360aa5472
--- /dev/null
+++ b/core/modules/system/tests/modules/dialog_renderer_test/dialog_renderer_test.services.yml
@@ -0,0 +1,13 @@
+services:
+# Provide 2 main content renderer services that use the same class but
+# behave differently depending on the 2nd argument.
+  main_content_renderer.wide_modal:
+    class: Drupal\dialog_renderer_test\Render\MainContent\WideModalRenderer
+    arguments: ['@title_resolver', 'wide']
+    tags:
+      - { name: render.main_content_renderer, format: drupal_modal.wide }
+  main_content_renderer.extra_wide_modal:
+    class: Drupal\dialog_renderer_test\Render\MainContent\WideModalRenderer
+    arguments: ['@title_resolver', 'extra_wide']
+    tags:
+      - { name: render.main_content_renderer, format: drupal_modal.extra_wide }
diff --git a/core/modules/system/tests/modules/dialog_renderer_test/src/Controller/TestController.php b/core/modules/system/tests/modules/dialog_renderer_test/src/Controller/TestController.php
new file mode 100644
index 0000000000..853c620dc2
--- /dev/null
+++ b/core/modules/system/tests/modules/dialog_renderer_test/src/Controller/TestController.php
@@ -0,0 +1,80 @@
+<?php
+
+namespace Drupal\dialog_renderer_test\Controller;
+
+use Drupal\Core\Url;
+
+/**
+ * Test controller display modal links and content.
+ */
+class TestController {
+
+  /**
+   * Return modal content.
+   *
+   * @return array
+   *   Render array for display in modal.
+   */
+  public function modalContent() {
+    return [
+      '#type' => 'markup',
+      '#markup' => 'Look at me in a modal!',
+    ];
+  }
+
+  /**
+   * Displays test links that will open in the modal dialog.
+   *
+   * @return array
+   *   Render array with links.
+   */
+  public function linksDisplay() {
+    return [
+      'normal_modal' => [
+        '#title' => 'Normal Modal!',
+        '#type' => 'link',
+        '#url' => Url::fromRoute('dialog_renderer_test.modal_content'),
+        '#attributes' => [
+          'class' => ['use-ajax'],
+          'data-dialog-type' => 'modal',
+        ],
+        '#attached' => [
+          'library' => [
+            'core/drupal.ajax',
+          ],
+        ],
+      ],
+      'wide_modal' => [
+        '#title' => 'Wide Modal!',
+        '#type' => 'link',
+        '#url' => Url::fromRoute('dialog_renderer_test.modal_content'),
+        '#attributes' => [
+          'class' => ['use-ajax'],
+          'data-dialog-type' => 'modal',
+          'data-dialog-renderer' => 'wide',
+        ],
+        '#attached' => [
+          'library' => [
+            'core/drupal.ajax',
+          ],
+        ],
+      ],
+      'extra_wide_modal' => [
+        '#title' => 'Extra Wide Modal!',
+        '#type' => 'link',
+        '#url' => Url::fromRoute('dialog_renderer_test.modal_content'),
+        '#attributes' => [
+          'class' => ['use-ajax'],
+          'data-dialog-type' => 'modal',
+          'data-dialog-renderer' => 'extra_wide',
+        ],
+        '#attached' => [
+          'library' => [
+            'core/drupal.ajax',
+          ],
+        ],
+      ],
+    ];
+  }
+
+}
diff --git a/core/modules/system/tests/modules/dialog_renderer_test/src/Render/MainContent/WideModalRenderer.php b/core/modules/system/tests/modules/dialog_renderer_test/src/Render/MainContent/WideModalRenderer.php
new file mode 100644
index 0000000000..3ca7715e36
--- /dev/null
+++ b/core/modules/system/tests/modules/dialog_renderer_test/src/Render/MainContent/WideModalRenderer.php
@@ -0,0 +1,78 @@
+<?php
+
+namespace Drupal\dialog_renderer_test\Render\MainContent;
+
+use Drupal\Core\Ajax\AjaxResponse;
+use Drupal\Core\Ajax\OpenModalDialogCommand;
+use Drupal\Core\Controller\TitleResolverInterface;
+use Drupal\Core\Render\MainContent\ModalRenderer;
+use Drupal\Core\Routing\RouteMatchInterface;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Default main content renderer for wide modal dialog requests.
+ *
+ * This test class is copied from \Drupal\Core\Render\MainContent\ModalRenderer
+ * to demonstrate selecting a different render via 'data-dialog-renderer' link
+ * attribute.
+ */
+class WideModalRenderer extends ModalRenderer {
+
+  /**
+   * The mode, either 'wide' or 'extra_wide'.
+   *
+   * @var string
+   */
+  protected $mode;
+
+  /**
+   * Constructs a new WideModalRenderer.
+   *
+   * @param \Drupal\Core\Controller\TitleResolverInterface $title_resolver
+   *   The title resolver.
+   * @param string $mode
+   *   The mode, either 'wide' or 'extra_wide'.
+   */
+  public function __construct(TitleResolverInterface $title_resolver, $mode = 'wide') {
+    parent::__construct($title_resolver);
+    $this->mode = $mode;
+  }
+
+
+  /**
+   * {@inheritdoc}
+   */
+  public function renderResponse(array $main_content, Request $request, RouteMatchInterface $route_match) {
+    $response = new AjaxResponse();
+
+    // First render the main content, because it might provide a title.
+    $content = drupal_render_root($main_content);
+
+    // Attach the library necessary for using the OpenModalDialogCommand and set
+    // the attachments for this Ajax response.
+    $main_content['#attached']['library'][] = 'core/drupal.dialog.ajax';
+    $response->setAttachments($main_content['#attached']);
+
+    // If the main content doesn't provide a title, use the title resolver.
+    $title = isset($main_content['#title']) ? $main_content['#title'] : $this->titleResolver->getTitle($request, $route_match->getRouteObject());
+
+    // Determine the title: use the title provided by the main content if any,
+    // otherwise get it from the routing information.
+    $options = $request->request->get('dialogOptions', []);
+    // Override width option.
+    switch ($this->mode) {
+      case 'wide':
+        $options['width'] = 700;
+        break;
+
+      case 'extra_wide':
+        $options['width'] = 1000;
+        break;
+    }
+
+
+    $response->addCommand(new OpenModalDialogCommand($title, $content, $options));
+    return $response;
+  }
+
+}
diff --git a/core/modules/system/tests/src/FunctionalJavascript/ModalRendererTest.php b/core/modules/system/tests/src/FunctionalJavascript/ModalRendererTest.php
new file mode 100644
index 0000000000..7c0aec022c
--- /dev/null
+++ b/core/modules/system/tests/src/FunctionalJavascript/ModalRendererTest.php
@@ -0,0 +1,38 @@
+<?php
+
+namespace Drupal\Tests\system\FunctionalJavascript;
+
+use Drupal\FunctionalJavascriptTests\JavascriptTestBase;
+
+/**
+ * Tests that dialog links use different renderer services.
+ *
+ * @group system
+ */
+class ModalRendererTest extends JavascriptTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['system', 'dialog_renderer_test'];
+
+  /**
+   * Tests that links respect 'data-dialog-renderer' attribute.
+   */
+  public function testModalRenderer() {
+    $session_assert = $this->assertSession();
+    $this->drupalGet('/dialog_renderer-test-links');
+    $this->clickLink('Normal Modal!');
+    // Neither of the wide modals should have been used.
+    $style = $session_assert->waitForElementVisible('css', '.ui-dialog')->getAttribute('style');
+    $this->assertNotContains('700px', $style);
+    $this->assertNotContains('1000px', $style);
+    $this->drupalGet('/dialog_renderer-test-links');
+    $this->clickLink('Wide Modal!');
+    $this->assertNotEmpty($session_assert->waitForElementVisible('css', '.ui-dialog[style*="width: 700px;"]'));
+    $this->drupalGet('/dialog_renderer-test-links');
+    $this->clickLink('Extra Wide Modal!');
+    $this->assertNotEmpty($session_assert->waitForElementVisible('css', '.ui-dialog[style*="width: 1000px;"]'));
+  }
+
+}
