diff --git a/core/core.libraries.yml b/core/core.libraries.yml
index 9cfd1cf..7698576 100644
--- a/core/core.libraries.yml
+++ b/core/core.libraries.yml
@@ -182,6 +182,22 @@ drupal.dialog.ajax:
     - core/drupal.ajax
     - core/drupal.dialog
 
+drupal.dialog.off_canvas:
+  version: VERSION
+  js:
+    misc/dialog/offcanvas.js: {}
+  css:
+    component:
+      misc/dialog/offcanvas.css: {}
+      misc/dialog/offcanvas.motion.css: {}
+  dependencies:
+    - core/jquery
+    - core/drupal
+    - core/drupal.ajax
+    - core/drupal.announce
+    - core/drupal.dialog
+    - core/drupal.dialog.ajax
+
 drupal.displace:
   version: VERSION
   js:
diff --git a/core/core.services.yml b/core/core.services.yml
index 8bce755..f8b1522 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -1059,6 +1059,11 @@ services:
     arguments: ['@title_resolver']
     tags:
       - { name: render.main_content_renderer, format: drupal_dialog }
+  main_content_renderer.off_canvas:
+    class: Drupal\Core\Render\MainContent\OffCanvasRender
+    arguments: ['@title_resolver', '@renderer']
+    tags:
+      - { name: render.main_content_renderer, format: drupal_dialog.offcanvas }
   main_content_renderer.modal:
     class: Drupal\Core\Render\MainContent\ModalRenderer
     arguments: ['@title_resolver']
diff --git a/core/modules/outside_in/src/Ajax/OpenOffCanvasDialogCommand.php b/core/lib/Drupal/Core/Ajax/OpenOffCanvasDialogCommand.php
similarity index 96%
rename from core/modules/outside_in/src/Ajax/OpenOffCanvasDialogCommand.php
rename to core/lib/Drupal/Core/Ajax/OpenOffCanvasDialogCommand.php
index d78dc35..fbb4761 100644
--- a/core/modules/outside_in/src/Ajax/OpenOffCanvasDialogCommand.php
+++ b/core/lib/Drupal/Core/Ajax/OpenOffCanvasDialogCommand.php
@@ -1,8 +1,6 @@
 <?php
 
-namespace Drupal\outside_in\Ajax;
-
-use Drupal\Core\Ajax\OpenDialogCommand;
+namespace Drupal\Core\Ajax;
 
 /**
  * Defines an AJAX command to open content in a dialog in a off-canvas tray.
diff --git a/core/modules/outside_in/src/Render/MainContent/OffCanvasRender.php b/core/lib/Drupal/Core/Render/MainContent/OffCanvasRender.php
similarity index 89%
rename from core/modules/outside_in/src/Render/MainContent/OffCanvasRender.php
rename to core/lib/Drupal/Core/Render/MainContent/OffCanvasRender.php
index 5d24c09..0729d3b 100644
--- a/core/modules/outside_in/src/Render/MainContent/OffCanvasRender.php
+++ b/core/lib/Drupal/Core/Render/MainContent/OffCanvasRender.php
@@ -1,13 +1,12 @@
 <?php
 
-namespace Drupal\outside_in\Render\MainContent;
+namespace Drupal\Core\Render\MainContent;
 
 use Drupal\Core\Ajax\AjaxResponse;
+use Drupal\Core\Ajax\OpenOffCanvasDialogCommand;
 use Drupal\Core\Controller\TitleResolverInterface;
-use Drupal\Core\Render\MainContent\DialogRenderer;
 use Drupal\Core\Render\RendererInterface;
 use Drupal\Core\Routing\RouteMatchInterface;
-use Drupal\outside_in\Ajax\OpenOffCanvasDialogCommand;
 use Symfony\Component\HttpFoundation\Request;
 
 /**
@@ -46,7 +45,7 @@ public function renderResponse(array $main_content, Request $request, RouteMatch
 
     // Attach the library necessary for using the OpenOffCanvasDialogCommand and
     // set the attachments for this Ajax response.
-    $main_content['#attached']['library'][] = 'outside_in/drupal.off_canvas';
+    $main_content['#attached']['library'][] = 'core/drupal.dialog.off_canvas';
     $response->setAttachments($main_content['#attached']);
 
     // If the main content doesn't provide a title, use the title resolver.
diff --git a/core/misc/ajax.js b/core/misc/ajax.js
index fefe9f3..fa32e05 100644
--- a/core/misc/ajax.js
+++ b/core/misc/ajax.js
@@ -64,6 +64,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;
@@ -312,6 +313,8 @@
    *   Options for {@link Drupal.dialog}.
    * @prop {string} [dialogType]
    *   One of `'modal'` or `'dialog'`.
+   * @prop {string} [drupalDialogRenderer]
+   *   Renderer for the dialog, 'offcanvas' supported.
    * @prop {string} [prevent]
    *   List of events on which to stop default action and stop propagation.
    */
@@ -526,7 +529,11 @@
     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;
 
     // Bind the ajaxSubmit function to the element event.
     $(ajax.element).on(element_settings.event, function (event) {
diff --git a/core/misc/dialog/offcanvas.css b/core/misc/dialog/offcanvas.css
new file mode 100644
index 0000000..0e46c6a
--- /dev/null
+++ b/core/misc/dialog/offcanvas.css
@@ -0,0 +1,39 @@
+/**
+ * @file
+ * CSS for Offcanvas tray.
+ */
+/* Position the dialog-offcanvas tray container outside the right of the viewport. */
+.ui-dialog-offcanvas {
+  box-sizing: border-box;
+  height: 100%;
+  overflow: visible;
+}
+
+/* Wrap the form that's inside the dialog-offcanvas tray. */
+.ui-dialog-offcanvas .ui-dialog-content {
+  padding: 0 20px;
+  /* Prevent horizontal scrollbar. */
+  overflow-x: hidden;
+  overflow-y: auto;
+}
+
+[dir="rtl"] .ui-dialog-offcanvas .ui-dialog-content {
+  text-align: right;
+}
+
+/*
+ * Force the tray to be 100% width at the same breakpoint the dialog system uses
+ * to expand dialog widths.
+ */
+@media all and (max-width: 48em) {
+  /* 768px */
+  .ui-dialog.ui-dialog-offcanvas {
+    width: 100% !important;
+  }
+
+  /* When tray is at 100% width stop the body from scrolling */
+  .js-tray-open {
+    height: 100%;
+    overflow-y: hidden;
+  }
+}
diff --git a/core/modules/outside_in/js/offcanvas.js b/core/misc/dialog/offcanvas.js
similarity index 94%
rename from core/modules/outside_in/js/offcanvas.js
rename to core/misc/dialog/offcanvas.js
index 8cda69d..7493e0a 100644
--- a/core/modules/outside_in/js/offcanvas.js
+++ b/core/misc/dialog/offcanvas.js
@@ -1,10 +1,6 @@
 /**
  * @file
  * Drupal's off-canvas library.
- *
- * @todo This functionality should extracted into a new core library or a part
- *  of the current drupal.dialog.ajax library.
- *  https://www.drupal.org/node/2784443
  */
 
 (function ($, Drupal, debounce, displace) {
@@ -12,7 +8,7 @@
   'use strict';
 
   // The minimum width to use body displace needs to match the width at which
-  // the tray will be %100 width. @see outside_in.module.css
+  // the tray will be %100 width. @see offcanvas.css
   var minDisplaceWidth = 768;
 
   /**
diff --git a/core/modules/outside_in/css/offcanvas.motion.css b/core/misc/dialog/offcanvas.motion.css
similarity index 50%
rename from core/modules/outside_in/css/offcanvas.motion.css
rename to core/misc/dialog/offcanvas.motion.css
index 1c06994..7228a29 100644
--- a/core/modules/outside_in/css/offcanvas.motion.css
+++ b/core/misc/dialog/offcanvas.motion.css
@@ -5,24 +5,23 @@
  * Motion effects are in a separate file so that they can be easily turned off
  * to improve performance if desired.
  *
- * @todo Move motion effects file into a core Off-Canvas library and add a
- *   configuration option for browser rendering performance to disable this
- *   file: https://www.drupal.org/node/2784443.
+ * @todo Add a configuration option for browser rendering performance to disable
+ *   this file: https://www.drupal.org/node/2784443.
  */
 
 /* Transition the dialog-offcanvas tray container, with 2s delay to match main canvas speed. */
 .ui-dialog-offcanvas .ui-dialog-content {
-    -webkit-transition: all .7s ease 2s;
-    -moz-transition: all .7s ease 2s;
-    transition: all .7s ease 2s;
+  -webkit-transition: all .7s ease 2s;
+  -moz-transition: all .7s ease 2s;
+  transition: all .7s ease 2s;
 }
 
 @media (max-width: 700px) {
-    .ui-dialog-offcanvas .ui-dialog-content {
-        -webkit-transition: all .7s ease;
-        -moz-transition: all .7s ease;
-        transition: all .7s ease;
-    }
+  .ui-dialog-offcanvas .ui-dialog-content {
+    -webkit-transition: all .7s ease;
+    -moz-transition: all .7s ease;
+    transition: all .7s ease;
+  }
 }
 
 .dialog-offcanvas__main-canvas {
diff --git a/core/modules/outside_in/css/offcanvas.css b/core/modules/outside_in/css/offcanvas.css
deleted file mode 100644
index fb671db..0000000
--- a/core/modules/outside_in/css/offcanvas.css
+++ /dev/null
@@ -1,23 +0,0 @@
-/**
- * @file
- * CSS for Offcanvas tray.
- *
- * @todo Move CSS into core dialog library https://www.drupal.org/node/2784443.
- */
-/* Position the dialog-offcanvas tray container outside the right of the viewport. */
-.ui-dialog-offcanvas {
-    box-sizing: border-box;
-    height: 100%;
-    overflow: visible;
-}
-
-/* Wrap the form that's inside the dialog-offcanvas tray. */
-.ui-dialog-offcanvas .ui-dialog-content {
-    padding: 0 20px;
-    /* Prevent horizontal scrollbar. */
-    overflow-x: hidden;
-    overflow-y: auto;
-}
-[dir="rtl"] .ui-dialog-offcanvas .ui-dialog-content {
-    text-align: right;
-}
diff --git a/core/modules/outside_in/css/outside_in.module.css b/core/modules/outside_in/css/outside_in.module.css
index 5ac00ab..2022837 100644
--- a/core/modules/outside_in/css/outside_in.module.css
+++ b/core/modules/outside_in/css/outside_in.module.css
@@ -21,18 +21,3 @@
 #main-canvas.js-outside-in-edit-mode .contextual-links a {
   pointer-events: inherit;
 }
-
-/*
- * Force the tray to be 100% width at the same breakpoint the dialog system uses
- * to expand dialog widths.
- */
-@media all and (max-width: 48em) { /* 768px */
-  .ui-dialog.ui-dialog-offcanvas {
-    width: 100% !important;
-  }
-  /* When tray is at 100% width stop the body from scrolling */
-  .js-tray-open {
-    height: 100%;
-    overflow-y: hidden;
-  }
-}
diff --git a/core/modules/outside_in/css/outside_in.motion.css b/core/modules/outside_in/css/outside_in.motion.css
index 450bffe..1177225 100644
--- a/core/modules/outside_in/css/outside_in.motion.css
+++ b/core/modules/outside_in/css/outside_in.motion.css
@@ -4,10 +4,6 @@
  *
  * Motion effects are in a separate file so that they can be easily turned off
  * to improve performance if desired.
- *
- * @todo Move motion effects file into a core Off-Canvas library and add a
- *   configuration option for browser rendering performance to disable this
- *   file: https://www.drupal.org/node/2784443.
  */
 
 
diff --git a/core/modules/outside_in/js/outside_in.js b/core/modules/outside_in/js/outside_in.js
index 36f85b0..650af46 100644
--- a/core/modules/outside_in/js/outside_in.js
+++ b/core/modules/outside_in/js/outside_in.js
@@ -214,26 +214,17 @@
     attach: function () {
 
       $(toggleEditSelector).once('outsidein').on('click.outsidein', toggleEditMode);
-
-      var search = Drupal.ajax.WRAPPER_FORMAT + '=drupal_dialog';
-      var replace = Drupal.ajax.WRAPPER_FORMAT + '=drupal_dialog_offcanvas';
-      // Loop through all Ajax links and change the format to dialog-offcanvas when
-      // needed.
+      // Loop through all Ajax links to set active editable ID.
       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') === 'offcanvas';
-            wrapperOffcanvas = instance.options.url.indexOf('drupal_dialog_offcanvas') === -1;
           }
-          return hasElement && rendererOffcanvas && wrapperOffcanvas;
+          return hasElement && rendererOffcanvas;
         })
         .forEach(function (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/outside_in.libraries.yml b/core/modules/outside_in/outside_in.libraries.yml
index 5eddea4..cf12dc0 100644
--- a/core/modules/outside_in/outside_in.libraries.yml
+++ b/core/modules/outside_in/outside_in.libraries.yml
@@ -20,14 +20,3 @@ drupal.outside_in:
     - core/drupal
     - core/jquery.once
     - core/drupal.ajax
-drupal.off_canvas:
-  version: VERSION
-  js:
-    js/offcanvas.js: {}
-  dependencies:
-    - core/jquery
-    - core/drupal
-    - core/drupal.ajax
-    - core/drupal.announce
-    - core/drupal.dialog
-    - core/drupal.dialog.ajax
diff --git a/core/modules/outside_in/outside_in.module b/core/modules/outside_in/outside_in.module
index a7fab7b..54a3525 100644
--- a/core/modules/outside_in/outside_in.module
+++ b/core/modules/outside_in/outside_in.module
@@ -41,7 +41,7 @@ function outside_in_contextual_links_view_alter(&$element, $items) {
       'data-outside-in-edit' => TRUE,
     ];
 
-    $element['#attached']['library'][] = 'outside_in/drupal.off_canvas';
+    $element['#attached']['library'][] = 'core/drupal.dialog.off_canvas';
   }
 }
 
@@ -58,26 +58,6 @@ function outside_in_block_view_alter(array &$build) {
 }
 
 /**
- * Implements hook_element_info_alter().
- */
-function outside_in_element_info_alter(&$type) {
-  if (isset($type['page'])) {
-    $type['page']['#theme_wrappers']['outside_in_page_wrapper'] = ['#weight' => -1000];
-  }
-}
-
-/**
- * Implements hook_theme().
- */
-function outside_in_theme() {
-  return [
-    'outside_in_page_wrapper' => [
-      'variables' => ['children' => NULL],
-    ],
-  ];
-}
-
-/**
  * Implements hook_entity_type_build().
  */
 function outside_in_entity_type_build(array &$entity_types) {
diff --git a/core/modules/outside_in/outside_in.services.yml b/core/modules/outside_in/outside_in.services.yml
index 48f5824..c678291 100644
--- a/core/modules/outside_in/outside_in.services.yml
+++ b/core/modules/outside_in/outside_in.services.yml
@@ -1,10 +1,4 @@
 services:
-  main_content_renderer.off_canvas:
-    class: Drupal\outside_in\Render\MainContent\OffCanvasRender
-    arguments: ['@title_resolver', '@renderer']
-    tags:
-      - { name: render.main_content_renderer, format: drupal_dialog_offcanvas }
-
   outside_in.manager:
     class: Drupal\outside_in\OutsideInManager
     arguments: ['@router.admin_context', '@current_route_match', '@current_user']
diff --git a/core/modules/outside_in/tests/modules/offcanvas_test/src/Plugin/Block/TestBlock.php b/core/modules/outside_in/tests/modules/offcanvas_test/src/Plugin/Block/TestBlock.php
deleted file mode 100644
index efb38e0..0000000
--- a/core/modules/outside_in/tests/modules/offcanvas_test/src/Plugin/Block/TestBlock.php
+++ /dev/null
@@ -1,49 +0,0 @@
-<?php
-
-namespace Drupal\offcanvas_test\Plugin\Block;
-
-use Drupal\Core\Block\BlockBase;
-use Drupal\Core\Url;
-
-/**
- * Provides an 'Off-canvas test block' block.
- *
- * @Block(
- *   id = "offcanvas_links_block",
- *   admin_label = @Translation("Off-canvas test block")
- * )
- */
-class TestBlock extends BlockBase {
-
-  /**
-   * {@inheritdoc}
-   */
-  public function build() {
-    return [
-      'offcanvas_link_1' => [
-        '#title' => $this->t('Click Me 1!'),
-        '#type' => 'link',
-        '#url' => Url::fromRoute('offcanvas_test.thing1'),
-        '#attributes' => [
-          'class' => ['use-ajax'],
-          'data-dialog-type' => 'offcanvas',
-        ],
-      ],
-      'offcanvas_link_2' => [
-        '#title' => $this->t('Click Me 2!'),
-        '#type' => 'link',
-        '#url' => Url::fromRoute('offcanvas_test.thing2'),
-        '#attributes' => [
-          'class' => ['use-ajax'],
-          'data-dialog-type' => 'offcanvas',
-        ],
-      ],
-      '#attached' => [
-        'library' => [
-          'outside_in/drupal.off_canvas',
-        ],
-      ],
-    ];
-  }
-
-}
diff --git a/core/modules/outside_in/tests/src/FunctionalJavascript/OutsideInJavascriptTestBase.php b/core/modules/outside_in/tests/src/FunctionalJavascript/OutsideInJavascriptTestBase.php
index 48ba899..a202211 100644
--- a/core/modules/outside_in/tests/src/FunctionalJavascript/OutsideInJavascriptTestBase.php
+++ b/core/modules/outside_in/tests/src/FunctionalJavascript/OutsideInJavascriptTestBase.php
@@ -2,92 +2,12 @@
 
 namespace Drupal\Tests\outside_in\FunctionalJavascript;
 
-use Drupal\FunctionalJavascriptTests\JavascriptTestBase;
+use Drupal\Tests\system\FunctionalJavascript\OffCanvasTestBase;
 
 /**
  * Base class contains common test functionality for the Settings Tray module.
  */
-abstract class OutsideInJavascriptTestBase extends JavascriptTestBase {
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function drupalGet($path, array $options = array(), array $headers = array()) {
-    $return = parent::drupalGet($path, $options, $headers);
-
-    // After the page loaded we need to additionally wait until the settings
-    // tray Ajax activity is done.
-    $this->assertSession()->assertWaitOnAjaxRequest();
-
-    return $return;
-  }
-
-  /**
-   * Enables a theme.
-   *
-   * @param string $theme
-   *   The theme.
-   */
-  public function enableTheme($theme) {
-    // Enable the theme.
-    \Drupal::service('theme_installer')->install([$theme]);
-    $theme_config = \Drupal::configFactory()->getEditable('system.theme');
-    $theme_config->set('default', $theme);
-    $theme_config->save();
-  }
-
-  /**
-   * Waits for Off-canvas tray to open.
-   */
-  protected function waitForOffCanvasToOpen() {
-    $web_assert = $this->assertSession();
-    $web_assert->assertWaitOnAjaxRequest();
-    $this->waitForElement('#drupal-offcanvas');
-  }
-
-  /**
-   * Waits for Off-canvas tray to close.
-   */
-  protected function waitForOffCanvasToClose() {
-    $this->waitForNoElement('#drupal-offcanvas');
-  }
-
-  /**
-   * Waits for an element to appear on the page.
-   *
-   * @param string $selector
-   *   CSS selector.
-   * @param int $timeout
-   *   (optional) Timeout in milliseconds, defaults to 10000.
-   */
-  protected function waitForElement($selector, $timeout = 10000) {
-    $condition = "(jQuery('$selector').length > 0)";
-    $this->assertJsCondition($condition, $timeout);
-  }
-
-  /**
-   * Gets the Off-Canvas tray element.
-   *
-   * @return \Behat\Mink\Element\NodeElement|null
-   */
-  protected function getTray() {
-    $tray = $this->getSession()->getPage()->find('css', '.ui-dialog[aria-describedby="drupal-offcanvas"]');
-    $this->assertEquals(FALSE, empty($tray), 'The tray was found.');
-    return $tray;
-  }
-
-  /**
-   * Waits for an element to be removed from the page.
-   *
-   * @param string $selector
-   *   CSS selector.
-   * @param int $timeout
-   *   (optional) Timeout in milliseconds, defaults to 10000.
-   */
-  protected function waitForNoElement($selector, $timeout = 10000) {
-    $condition = "(jQuery('$selector').length == 0)";
-    $this->assertJsCondition($condition, $timeout);
-  }
+abstract class OutsideInJavascriptTestBase extends OffCanvasTestBase {
 
   /**
    * Clicks a contextual link.
diff --git a/core/modules/outside_in/src/Tests/Ajax/OffCanvasDialogTest.php b/core/modules/system/src/Tests/Ajax/OffCanvasDialogTest.php
similarity index 87%
rename from core/modules/outside_in/src/Tests/Ajax/OffCanvasDialogTest.php
rename to core/modules/system/src/Tests/Ajax/OffCanvasDialogTest.php
index f351042..0debdc8 100644
--- a/core/modules/outside_in/src/Tests/Ajax/OffCanvasDialogTest.php
+++ b/core/modules/system/src/Tests/Ajax/OffCanvasDialogTest.php
@@ -1,26 +1,18 @@
 <?php
 
-namespace Drupal\outside_in\Tests\Ajax;
+namespace Drupal\system\Tests\Ajax;
 
 use Drupal\ajax_test\Controller\AjaxTestController;
 use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
-use Drupal\system\Tests\Ajax\AjaxTestBase;
 
 /**
  * Performs tests on opening and manipulating dialogs via AJAX commands.
  *
- * @group outside_in
+ * @group Ajax
  */
 class OffCanvasDialogTest extends AjaxTestBase {
 
   /**
-   * Modules to enable.
-   *
-   * @var array
-   */
-  public static $modules = ['outside_in'];
-
-  /**
    * Test sending AJAX requests to open and manipulate offcanvas dialog.
    */
   public function testDialog() {
@@ -52,7 +44,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_offcanvas']]);
+    $ajax_result = $this->drupalGetAjax('ajax-test/dialog-contents', ['query' => [MainContentViewSubscriber::WRAPPER_FORMAT => 'drupal_dialog.offcanvas']]);
     $this->assertEqual($offcanvas_expected_response, $ajax_result[3], 'Off-canvas dialog JSON response matches.');
   }
 
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index 6581e60..043fc0f 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -224,6 +224,9 @@ function system_theme() {
       ),
       'template' => 'entity-add-list',
     ),
+    'offcanvas_page_wrapper' => [
+      'variables' => ['children' => NULL],
+    ],
   ));
 }
 
@@ -1445,3 +1448,12 @@ function system_query_entity_reference_alter(AlterableInterface $query) {
   $handler = $query->getMetadata('entity_reference_selection_handler');
   $handler->entityQueryAlter($query);
 }
+
+/**
+ * Implements hook_element_info_alter().
+ */
+function system_element_info_alter(&$type) {
+  if (isset($type['page'])) {
+    $type['page']['#theme_wrappers']['offcanvas_page_wrapper'] = ['#weight' => -1000];
+  }
+}
diff --git a/core/modules/outside_in/templates/outside-in-page-wrapper.html.twig b/core/modules/system/templates/offcanvas-page-wrapper.html.twig
similarity index 100%
rename from core/modules/outside_in/templates/outside-in-page-wrapper.html.twig
rename to core/modules/system/templates/offcanvas-page-wrapper.html.twig
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 0000000..f7544e7
--- /dev/null
+++ b/core/modules/system/tests/modules/dialog_renderer_test/dialog_renderer_test.info.yml
@@ -0,0 +1,6 @@
+name: 'Database 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 0000000..3cfbde0
--- /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 0000000..1bf58fa
--- /dev/null
+++ b/core/modules/system/tests/modules/dialog_renderer_test/dialog_renderer_test.services.yml
@@ -0,0 +1,6 @@
+services:
+  main_content_renderer.wide_modal:
+    class: Drupal\dialog_renderer_test\Render\MainContent\WideModalRenderer
+    arguments: ['@title_resolver', '@renderer']
+    tags:
+      - { name: render.main_content_renderer, format: drupal_modal.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 0000000..573a656
--- /dev/null
+++ b/core/modules/system/tests/modules/dialog_renderer_test/src/Controller/TestController.php
@@ -0,0 +1,65 @@
+<?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',
+          ],
+        ],
+      ],
+    ];
+  }
+
+}
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 0000000..6309f24
--- /dev/null
+++ b/core/modules/system/tests/modules/dialog_renderer_test/src/Render/MainContent/WideModalRenderer.php
@@ -0,0 +1,47 @@
+<?php
+
+namespace Drupal\dialog_renderer_test\Render\MainContent;
+
+use Drupal\Core\Ajax\AjaxResponse;
+use Drupal\Core\Ajax\OpenModalDialogCommand;
+use Drupal\Core\Render\MainContent\DialogRenderer;
+use Drupal\Core\Routing\RouteMatchInterface;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Wide main content renderer for 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 DialogRenderer {
+
+  /**
+   * {@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.
+    $options['width'] = 700;
+
+    $response->addCommand(new OpenModalDialogCommand($title, $content, $options));
+    return $response;
+  }
+
+}
diff --git a/core/modules/outside_in/tests/modules/offcanvas_test/offcanvas_test.info.yml b/core/modules/system/tests/modules/offcanvas_test/offcanvas_test.info.yml
similarity index 91%
rename from core/modules/outside_in/tests/modules/offcanvas_test/offcanvas_test.info.yml
rename to core/modules/system/tests/modules/offcanvas_test/offcanvas_test.info.yml
index 8c6cc80..cb3dd2c 100644
--- a/core/modules/outside_in/tests/modules/offcanvas_test/offcanvas_test.info.yml
+++ b/core/modules/system/tests/modules/offcanvas_test/offcanvas_test.info.yml
@@ -6,4 +6,3 @@ version: VERSION
 core: 8.x
 dependencies:
   - block
-  - outside_in
diff --git a/core/modules/outside_in/tests/modules/offcanvas_test/offcanvas_test.routing.yml b/core/modules/system/tests/modules/offcanvas_test/offcanvas_test.routing.yml
similarity index 100%
rename from core/modules/outside_in/tests/modules/offcanvas_test/offcanvas_test.routing.yml
rename to core/modules/system/tests/modules/offcanvas_test/offcanvas_test.routing.yml
diff --git a/core/modules/outside_in/tests/modules/offcanvas_test/src/Controller/TestController.php b/core/modules/system/tests/modules/offcanvas_test/src/Controller/TestController.php
similarity index 94%
rename from core/modules/outside_in/tests/modules/offcanvas_test/src/Controller/TestController.php
rename to core/modules/system/tests/modules/offcanvas_test/src/Controller/TestController.php
index 9ee8e17..e5fae3d 100644
--- a/core/modules/outside_in/tests/modules/offcanvas_test/src/Controller/TestController.php
+++ b/core/modules/system/tests/modules/offcanvas_test/src/Controller/TestController.php
@@ -55,7 +55,7 @@ public function linksDisplay() {
         ],
         '#attached' => [
           'library' => [
-            'outside_in/drupal.outside_in',
+            'core/drupal.ajax',
           ],
         ],
       ],
@@ -73,7 +73,7 @@ public function linksDisplay() {
         ],
         '#attached' => [
           'library' => [
-            'outside_in/drupal.outside_in',
+            'core/drupal.ajax',
           ],
         ],
       ],
@@ -88,7 +88,7 @@ public function linksDisplay() {
         ],
         '#attached' => [
           'library' => [
-            'outside_in/drupal.outside_in',
+            'core/drupal.ajax',
           ],
         ],
       ],
@@ -131,7 +131,7 @@ public function otherDialogLinks() {
       ],
       '#attached' => [
         'library' => [
-          'outside_in/drupal.outside_in',
+          'core/drupal.ajax',
         ],
       ],
     ];
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 0000000..ce234a1
--- /dev/null
+++ b/core/modules/system/tests/src/FunctionalJavascript/ModalRendererTest.php
@@ -0,0 +1,34 @@
+<?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!');
+    $session_assert->assertWaitOnAjaxRequest();
+    $session_assert->elementAttributeNotContains('css', '.ui-dialog', 'style', 'width: 700px;');
+    $this->drupalGet('/dialog_renderer-test-links');
+    $this->clickLink('Wide Modal!');
+    $session_assert->assertWaitOnAjaxRequest();
+    $session_assert->elementAttributeContains('css', '.ui-dialog', 'style', 'width: 700px;');
+  }
+
+}
diff --git a/core/modules/outside_in/tests/src/FunctionalJavascript/OffCanvasTest.php b/core/modules/system/tests/src/FunctionalJavascript/OffCanvasTest.php
similarity index 94%
rename from core/modules/outside_in/tests/src/FunctionalJavascript/OffCanvasTest.php
rename to core/modules/system/tests/src/FunctionalJavascript/OffCanvasTest.php
index b81c723..dc4b332 100644
--- a/core/modules/outside_in/tests/src/FunctionalJavascript/OffCanvasTest.php
+++ b/core/modules/system/tests/src/FunctionalJavascript/OffCanvasTest.php
@@ -1,18 +1,18 @@
 <?php
 
-namespace Drupal\Tests\outside_in\FunctionalJavascript;
+namespace Drupal\Tests\system\FunctionalJavascript;
 
 /**
  * Tests the off-canvas tray functionality.
  *
- * @group outside_in
+ * @group system
  */
-class OffCanvasTest extends OutsideInJavascriptTestBase {
+class OffCanvasTest extends OffCanvasTestBase {
 
   /**
    * {@inheritdoc}
    */
-  public static $modules = ['block', 'system', 'toolbar', 'outside_in', 'offcanvas_test'];
+  public static $modules = ['block', 'system', 'toolbar', 'offcanvas_test'];
 
   /**
    * Tests that regular non-contextual links will work with the off-canvas tray.
diff --git a/core/modules/system/tests/src/FunctionalJavascript/OffCanvasTestBase.php b/core/modules/system/tests/src/FunctionalJavascript/OffCanvasTestBase.php
new file mode 100644
index 0000000..9358b08
--- /dev/null
+++ b/core/modules/system/tests/src/FunctionalJavascript/OffCanvasTestBase.php
@@ -0,0 +1,93 @@
+<?php
+
+namespace Drupal\Tests\system\FunctionalJavascript;
+
+use Drupal\FunctionalJavascriptTests\JavascriptTestBase;
+
+/**
+ * Base class contains common test functionality the off-canvas tray.
+ */
+abstract class OffCanvasTestBase extends JavascriptTestBase {
+
+  /**
+   * Waits for Off-canvas tray to open.
+   */
+  protected function waitForOffCanvasToOpen() {
+    $web_assert = $this->assertSession();
+    $web_assert->assertWaitOnAjaxRequest();
+    $this->waitForElement('#drupal-offcanvas');
+  }
+
+  /**
+   * Waits for an element to appear on the page.
+   *
+   * @param string $selector
+   *   CSS selector.
+   * @param int $timeout
+   *   (optional) Timeout in milliseconds, defaults to 10000.
+   */
+  protected function waitForElement($selector, $timeout = 10000) {
+    $condition = "(jQuery('$selector').length > 0)";
+    $this->assertJsCondition($condition, $timeout);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function drupalGet($path, array $options = [], array $headers = []) {
+    $return = parent::drupalGet($path, $options, $headers);
+
+    // After the page loaded we need to additionally wait until the settings
+    // tray Ajax activity is done.
+    $this->assertSession()->assertWaitOnAjaxRequest();
+
+    return $return;
+  }
+
+  /**
+   * Enables a theme.
+   *
+   * @param string $theme
+   *   The theme.
+   */
+  public function enableTheme($theme) {
+    // Enable the theme.
+    \Drupal::service('theme_installer')->install([$theme]);
+    $theme_config = \Drupal::configFactory()->getEditable('system.theme');
+    $theme_config->set('default', $theme);
+    $theme_config->save();
+  }
+
+  /**
+   * Waits for Off-canvas tray to close.
+   */
+  protected function waitForOffCanvasToClose() {
+    $this->waitForNoElement('#drupal-offcanvas');
+  }
+
+  /**
+   * Waits for an element to be removed from the page.
+   *
+   * @param string $selector
+   *   CSS selector.
+   * @param int $timeout
+   *   (optional) Timeout in milliseconds, defaults to 10000.
+   */
+  protected function waitForNoElement($selector, $timeout = 10000) {
+    $condition = "(jQuery('$selector').length == 0)";
+    $this->assertJsCondition($condition, $timeout);
+  }
+
+  /**
+   * Gets the Off-Canvas tray element.
+   *
+   * @return \Behat\Mink\Element\NodeElement|null
+   *   The tray page element.
+   */
+  protected function getTray() {
+    $tray = $this->getSession()->getPage()->find('css', '.ui-dialog[aria-describedby="drupal-offcanvas"]');
+    $this->assertEquals(FALSE, empty($tray), 'The tray was found.');
+    return $tray;
+  }
+
+}
diff --git a/core/modules/outside_in/tests/src/Unit/Ajax/OpenOffCanvasDialogCommandTest.php b/core/modules/system/tests/src/Unit/Ajax/OpenOffCanvasDialogCommandTest.php
similarity index 80%
rename from core/modules/outside_in/tests/src/Unit/Ajax/OpenOffCanvasDialogCommandTest.php
rename to core/modules/system/tests/src/Unit/Ajax/OpenOffCanvasDialogCommandTest.php
index cd14d6d..5ef060f 100644
--- a/core/modules/outside_in/tests/src/Unit/Ajax/OpenOffCanvasDialogCommandTest.php
+++ b/core/modules/system/tests/src/Unit/Ajax/OpenOffCanvasDialogCommandTest.php
@@ -1,13 +1,13 @@
 <?php
 
-namespace Drupal\Tests\outside_in\Unit\Ajax;
+namespace Drupal\Tests\system\Unit\Ajax;
 
-use Drupal\outside_in\Ajax\OpenOffCanvasDialogCommand;
+use Drupal\Core\Ajax\OpenOffCanvasDialogCommand;
 use Drupal\Tests\UnitTestCase;
 
 /**
- * @coversDefaultClass \Drupal\outside_in\Ajax\OpenOffCanvasDialogCommand
- * @group outside_in
+ * @coversDefaultClass \Drupal\Core\Ajax\OpenOffCanvasDialogCommand
+ * @group Ajax
  */
 class OpenOffCanvasDialogCommandTest extends UnitTestCase {
 
diff --git a/core/themes/stable/css/core/dialog/offcanvas.css b/core/themes/stable/css/core/dialog/offcanvas.css
new file mode 100644
index 0000000..0e46c6a
--- /dev/null
+++ b/core/themes/stable/css/core/dialog/offcanvas.css
@@ -0,0 +1,39 @@
+/**
+ * @file
+ * CSS for Offcanvas tray.
+ */
+/* Position the dialog-offcanvas tray container outside the right of the viewport. */
+.ui-dialog-offcanvas {
+  box-sizing: border-box;
+  height: 100%;
+  overflow: visible;
+}
+
+/* Wrap the form that's inside the dialog-offcanvas tray. */
+.ui-dialog-offcanvas .ui-dialog-content {
+  padding: 0 20px;
+  /* Prevent horizontal scrollbar. */
+  overflow-x: hidden;
+  overflow-y: auto;
+}
+
+[dir="rtl"] .ui-dialog-offcanvas .ui-dialog-content {
+  text-align: right;
+}
+
+/*
+ * Force the tray to be 100% width at the same breakpoint the dialog system uses
+ * to expand dialog widths.
+ */
+@media all and (max-width: 48em) {
+  /* 768px */
+  .ui-dialog.ui-dialog-offcanvas {
+    width: 100% !important;
+  }
+
+  /* When tray is at 100% width stop the body from scrolling */
+  .js-tray-open {
+    height: 100%;
+    overflow-y: hidden;
+  }
+}
diff --git a/core/themes/stable/css/core/dialog/offcanvas.motion.css b/core/themes/stable/css/core/dialog/offcanvas.motion.css
new file mode 100644
index 0000000..7228a29
--- /dev/null
+++ b/core/themes/stable/css/core/dialog/offcanvas.motion.css
@@ -0,0 +1,31 @@
+/**
+ * @file
+ * Motion effects for off-canvas tray dialog.
+ *
+ * Motion effects are in a separate file so that they can be easily turned off
+ * to improve performance if desired.
+ *
+ * @todo Add a configuration option for browser rendering performance to disable
+ *   this file: https://www.drupal.org/node/2784443.
+ */
+
+/* Transition the dialog-offcanvas tray container, with 2s delay to match main canvas speed. */
+.ui-dialog-offcanvas .ui-dialog-content {
+  -webkit-transition: all .7s ease 2s;
+  -moz-transition: all .7s ease 2s;
+  transition: all .7s ease 2s;
+}
+
+@media (max-width: 700px) {
+  .ui-dialog-offcanvas .ui-dialog-content {
+    -webkit-transition: all .7s ease;
+    -moz-transition: all .7s ease;
+    transition: all .7s ease;
+  }
+}
+
+.dialog-offcanvas__main-canvas {
+  -webkit-transition: all .7s ease;
+  -moz-transition: all .7s ease;
+  transition: all .7s ease;
+}
diff --git a/core/themes/stable/stable.info.yml b/core/themes/stable/stable.info.yml
index 7e585b1..7efa1e0 100644
--- a/core/themes/stable/stable.info.yml
+++ b/core/themes/stable/stable.info.yml
@@ -65,6 +65,11 @@ libraries-override:
     css:
       component:
         misc/vertical-tabs.css: css/core/vertical-tabs.css
+  core/drupal.dialog.off_canvas:
+      css:
+        component:
+          misc/dialog/offcanvas.css: css/core/dialog/offcanvas.css
+          misc/dialog/offcanvas.motion.css: css/core/dialog/offcanvas.motion.css
 
   dblog/drupal.dblog:
     css:
diff --git a/core/themes/stable/templates/layout/offcanvas-page-wrapper.html.twig b/core/themes/stable/templates/layout/offcanvas-page-wrapper.html.twig
new file mode 100644
index 0000000..adedc6b
--- /dev/null
+++ b/core/themes/stable/templates/layout/offcanvas-page-wrapper.html.twig
@@ -0,0 +1,18 @@
+{#
+/**
+ * @file
+ * Default theme implementation for a page wrapper.
+ *
+ * For consistent wrapping to {{ page }} render in all themes.
+ *
+ * Available variables:
+ * - children: Contains the child elements of the page.
+ *
+ * @ingroup themeable
+ */
+#}
+{% if children %}
+  <div class="dialog-offcanvas__main-canvas" data-offcanvas-main-canvas >
+    {{ children }}
+  </div>
+{% endif %}
